From 3292cef4084a4556df40168e320f43c53e4d8462 Mon Sep 17 00:00:00 2001 From: JackN Date: Fri, 18 Mar 2022 08:50:04 -0400 Subject: [PATCH 01/29] new debugger option for svd file --- debugger/src/debugger.rs | 104 +++++++++++++++++++++------------------ 1 file changed, 56 insertions(+), 48 deletions(-) diff --git a/debugger/src/debugger.rs b/debugger/src/debugger.rs index 0da8eed804..9a0db5a8bc 100644 --- a/debugger/src/debugger.rs +++ b/debugger/src/debugger.rs @@ -107,6 +107,10 @@ pub struct DebuggerOptions { #[clap(long, parse(from_os_str))] pub(crate) program_binary: Option, + /// CMSIS-SVD file for the target. Relative to `cwd`, or fully qualified. + #[clap(long, parse(from_os_str))] + pub(crate) svd_file: Option, + /// The number associated with the debug probe to use. Use 'list' command to see available probes #[clap( long = "probe", @@ -209,12 +213,12 @@ impl DebuggerOptions { }; } - /// If the path to the programm to be debugged is relative, we join if with the cwd. - pub(crate) fn qualify_and_update_program_binary( + /// If the path to the program to be debugged is relative, we join if with the cwd. + pub(crate) fn qualify_and_update_os_file_path( &mut self, - new_program_binary: Option, - ) -> Result<(), DebuggerError> { - self.program_binary = match new_program_binary { + os_file_to_validate: Option, + ) -> Result { + match os_file_to_validate { Some(temp_path) => { let mut new_path = PathBuf::new(); if temp_path.is_relative() { @@ -228,11 +232,10 @@ impl DebuggerOptions { } } new_path.push(temp_path); - Some(new_path) + Ok(new_path) } - None => None, - }; - Ok(()) + None => Err(DebuggerError::Other(anyhow!("Missing value for file."))), + } } } @@ -1077,7 +1080,7 @@ impl Debugger { // Process either the Launch or Attach request. let requested_target_session_type: Option; - let la_request = loop { + let launch_attach_request = loop { let current_request = if let Some(request) = debug_adapter.listen_for_request()? { request } else { @@ -1108,7 +1111,7 @@ impl Debugger { }; }; - match get_arguments(&la_request) { + match get_arguments(&launch_attach_request) { Ok(arguments) => { if requested_target_session_type.is_some() { self.debugger_options = DebuggerOptions { ..arguments }; @@ -1124,7 +1127,7 @@ impl Debugger { || self.debugger_options.restore_unwritten_bytes { debug_adapter.send_response::<()>( - la_request, + launch_attach_request, Err(DebuggerError::Other(anyhow!( "Please do not use any of the `flashing_enabled`, `reset_after_flashing`, halt_after_reset`, `full_chip_erase`, or `restore_unwritten_bytes` options when using `attach` request type."))), )?; @@ -1142,28 +1145,17 @@ impl Debugger { // Update the `cwd` and `program_binary`. self.debugger_options .validate_and_update_cwd(self.debugger_options.cwd.clone()); - match self + // Update the `program_binary` and validate that the file exists. + self.debugger_options.program_binary = match self .debugger_options - .qualify_and_update_program_binary(self.debugger_options.program_binary.clone()) + .qualify_and_update_os_file_path(self.debugger_options.program_binary.clone()) { - Ok(_) => {} - Err(error) => { - let err = DebuggerError::Other(anyhow!( - "Unable to validate the program_binary path '{:?}'", - error - )); - - debug_adapter.send_error_response(&err)?; - return Err(err); - } - } - match self.debugger_options.program_binary.clone() { - Some(program_binary) => { + Ok(program_binary) => { if !program_binary.is_file() { debug_adapter.send_response::<()>( - la_request, + launch_attach_request, Err(DebuggerError::Other(anyhow!( - "Invalid program binary file specified '{:?}'", + "program_binary file {:?} not found.", program_binary ))), )?; @@ -1172,37 +1164,53 @@ impl Debugger { program_binary ))); } + Some(program_binary) } - None => { + Err(error) => { debug_adapter.send_response::<()>( - la_request, + launch_attach_request, Err(DebuggerError::Other(anyhow!( - "Please use the --program-binary option to specify an executable" + "Please use the --program-binary option to specify an executable: {:?}", error ))), )?; - return Err(DebuggerError::Other(anyhow!( "Please use the --program-binary option to specify an executable" ))); } - } - debug_adapter.send_response::<()>(la_request, Ok(None))?; + }; + // Update the `svd_file` and validate that the file exists. + // If there is a problem with this file, warn the user and continue with the session. + self.debugger_options.svd_file = match self + .debugger_options + .qualify_and_update_os_file_path(self.debugger_options.svd_file.clone()) + { + Ok(svd_file) => { + if !svd_file.is_file() { + debug_adapter.show_message( + MessageSeverity::Warning, + format!("SVD file {:?} not found.", svd_file), + ); + None + } else { + Some(svd_file) + } + } + Err(error) => { + // SVD file is not mandatory. + log::debug!("SVD file not specified: {:?}", &error); + None + } + }; + debug_adapter.send_response::<()>(launch_attach_request, Ok(None))?; } Err(error) => { - let err_1 = anyhow!( - - "Could not derive DebuggerOptions from request '{}', with arguments {:?}\n{:?} ", la_request.command, la_request.arguments, error - - ); - let err_2 =anyhow!( - - "Could not derive DebuggerOptions from request '{}', with arguments {:?}\n{:?} ", la_request.command, la_request.arguments, error - - ); - - debug_adapter.send_response::<()>(la_request, Err(DebuggerError::Other(err_1)))?; + let error_message = format!("Could not derive DebuggerOptions from request '{}', with arguments {:?}\n{:?} ", launch_attach_request.command, launch_attach_request.arguments, error); + debug_adapter.send_response::<()>( + launch_attach_request, + Err(DebuggerError::Other(anyhow!(error_message.clone()))), + )?; - return Err(DebuggerError::Other(err_2)); + return Err(DebuggerError::Other(anyhow!(error_message))); } }; From 088b5ae1c2883499938fac6ee2f2dcfa377df3f7 Mon Sep 17 00:00:00 2001 From: JackN Date: Fri, 18 Mar 2022 13:02:40 -0400 Subject: [PATCH 02/29] Break apart large files in probe-rs/debugger/* --- .vscode/launch.json | 21 + .../dap_adapter.rs} | 25 +- debugger/src/{ => debug_adapter}/dap_types.rs | 2 +- .../{ => debug_adapter}/debugProtocol.json | 0 debugger/src/debug_adapter/mod.rs | 6 + debugger/src/{ => debug_adapter}/protocol.rs | 24 +- debugger/src/debugger/configuration.rs | 159 ++++ debugger/src/debugger/core_data.rs | 125 +++ .../{debugger.rs => debugger/debug_entry.rs} | 761 ++---------------- debugger/src/debugger/debug_rtt.rs | 76 ++ debugger/src/debugger/mod.rs | 10 + debugger/src/debugger/session_data.rs | 231 ++++++ debugger/src/main.rs | 22 +- 13 files changed, 743 insertions(+), 719 deletions(-) rename debugger/src/{debug_adapter.rs => debug_adapter/dap_adapter.rs} (99%) rename debugger/src/{ => debug_adapter}/dap_types.rs (98%) rename debugger/src/{ => debug_adapter}/debugProtocol.json (100%) create mode 100644 debugger/src/debug_adapter/mod.rs rename debugger/src/{ => debug_adapter}/protocol.rs (97%) create mode 100644 debugger/src/debugger/configuration.rs create mode 100644 debugger/src/debugger/core_data.rs rename debugger/src/{debugger.rs => debugger/debug_entry.rs} (63%) create mode 100644 debugger/src/debugger/debug_rtt.rs create mode 100644 debugger/src/debugger/mod.rs create mode 100644 debugger/src/debugger/session_data.rs diff --git a/.vscode/launch.json b/.vscode/launch.json index 1f75677f26..ee01283389 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -100,6 +100,27 @@ "help", ], "cwd": "${workspaceFolder}/debugger" + }, + { + "type": "lldb", + "request": "launch", + "name": "display debug subcommand help text", + "cargo": { + "args": [ + "build", + "--bin=probe-rs-debugger", + "--package=probe-rs-debugger" + ], + "filter": { + "name": "probe-rs-debugger", + "kind": "bin" + } + }, + "args": [ + "help", + "debug", + ], + "cwd": "${workspaceFolder}/debugger" } ] } \ No newline at end of file diff --git a/debugger/src/debug_adapter.rs b/debugger/src/debug_adapter/dap_adapter.rs similarity index 99% rename from debugger/src/debug_adapter.rs rename to debugger/src/debug_adapter/dap_adapter.rs index 23e9852409..7565873ee5 100644 --- a/debugger/src/debug_adapter.rs +++ b/debugger/src/debug_adapter/dap_adapter.rs @@ -1,24 +1,19 @@ -use crate::dap_types; -use crate::debugger::BreakpointType; -use crate::debugger::ConsoleLog; -use crate::debugger::CoreData; -use crate::DebuggerError; +use crate::{ + debug_adapter::{dap_types, protocol::ProtocolAdapter}, + debugger::{configuration::ConsoleLog, core_data::CoreData, session_data::BreakpointType}, + DebuggerError, +}; use anyhow::{anyhow, Result}; use dap_types::*; use num_traits::Zero; use parse_int::parse; -use probe_rs::debug::Registers; -use probe_rs::debug::SourceLocation; -use probe_rs::debug::SteppingMode; -use probe_rs::debug::VariableLocation; -use probe_rs::debug::{VariableCache, VariableName}; -use probe_rs::{debug::ColumnType, CoreStatus, HaltReason, MemoryInterface}; +use probe_rs::{ + debug::{ColumnType, Registers, SourceLocation, VariableCache, VariableName}, + CoreStatus, HaltReason, MemoryInterface, +}; use probe_rs_cli_util::rtt; use serde::{de::DeserializeOwned, Serialize}; -use std::string::ToString; -use std::{convert::TryInto, path::Path, str, thread, time::Duration}; - -use crate::protocol::ProtocolAdapter; +use std::{convert::TryInto, path::Path, str, string::ToString, thread, time::Duration}; /// Progress ID used for progress reporting when the debug adapter protocol is used. type ProgressId = i64; diff --git a/debugger/src/dap_types.rs b/debugger/src/debug_adapter/dap_types.rs similarity index 98% rename from debugger/src/dap_types.rs rename to debugger/src/debug_adapter/dap_types.rs index f81081eaa6..440eb89f91 100644 --- a/debugger/src/dap_types.rs +++ b/debugger/src/debug_adapter/dap_types.rs @@ -7,7 +7,7 @@ use schemafy::schemafy; use serde::{Deserialize, Serialize}; use std::convert::TryFrom; -schemafy!(root: debugserver_types "src/debugProtocol.json"); +schemafy!(root: debugserver_types "src/debug_adapter/debugProtocol.json"); /// Custom 'quit' request, so that VSCode can tell the `probe-rs-debugger` to terminate its own process. #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] diff --git a/debugger/src/debugProtocol.json b/debugger/src/debug_adapter/debugProtocol.json similarity index 100% rename from debugger/src/debugProtocol.json rename to debugger/src/debug_adapter/debugProtocol.json diff --git a/debugger/src/debug_adapter/mod.rs b/debugger/src/debug_adapter/mod.rs new file mode 100644 index 0000000000..e12a0f7766 --- /dev/null +++ b/debugger/src/debug_adapter/mod.rs @@ -0,0 +1,6 @@ +/// Implements the logic for each of the MS DAP types (events, requests, etc.) +pub(crate) mod dap_adapter; +/// The MS DAP api, and extensions, for communicating with the MS DAP client. +pub(crate) mod dap_types; +/// Communication interfaces to connect the DAP client and probe-rs-debugger. +pub(crate) mod protocol; diff --git a/debugger/src/protocol.rs b/debugger/src/debug_adapter/protocol.rs similarity index 97% rename from debugger/src/protocol.rs rename to debugger/src/debug_adapter/protocol.rs index 945839976e..c97cf44519 100644 --- a/debugger/src/protocol.rs +++ b/debugger/src/debug_adapter/protocol.rs @@ -1,20 +1,20 @@ -use crate::debugger::ConsoleLog; -use crate::DebuggerError; +use crate::{ + debug_adapter::dap_types::{ + Event, MessageSeverity, OutputEventBody, ProtocolMessage, Request, Response, + ShowMessageEventBody, + }, + debugger::configuration::ConsoleLog, + DebuggerError, +}; +use anyhow::anyhow; use serde::Serialize; -use std::collections::HashMap; -use std::string::ToString; use std::{ + collections::HashMap, io::{BufRead, BufReader, Read, Write}, str, + string::ToString, }; -use crate::dap_types::{ - Event, MessageSeverity, OutputEventBody, ProtocolMessage, Request, Response, - ShowMessageEventBody, -}; - -use anyhow::anyhow; - pub trait ProtocolAdapter { /// Listen for a request. This call should be non-blocking, and if not request is available, it should /// return None. @@ -404,7 +404,7 @@ fn get_content_len(header: &str) -> Option { mod test { use std::io::{self, ErrorKind, Read}; - use crate::protocol::{get_content_len, ProtocolAdapter}; + use crate::debug_adapter::protocol::{get_content_len, ProtocolAdapter}; use super::DapAdapter; diff --git a/debugger/src/debugger/configuration.rs b/debugger/src/debugger/configuration.rs new file mode 100644 index 0000000000..86272c0679 --- /dev/null +++ b/debugger/src/debugger/configuration.rs @@ -0,0 +1,159 @@ +use crate::DebuggerError; +use anyhow::{anyhow, Result}; +use probe_rs::{DebugProbeSelector, WireProtocol}; +use probe_rs_cli_util::rtt; +use serde::Deserialize; +use std::{env::current_dir, path::PathBuf, str::FromStr}; + +/// Shared options for all session level configuration. +#[derive(Clone, Deserialize, Debug, Default)] +#[serde(rename_all = "camelCase")] +pub struct SessionConfig { + /// Path to the requested working directory for the debugger + pub(crate) cwd: Option, + + /// Binary to debug as a path. Relative to `cwd`, or fully qualified. + pub(crate) program_binary: Option, + + /// CMSIS-SVD file for the target. Relative to `cwd`, or fully qualified. + pub(crate) svd_file: Option, + + /// The number associated with the debug probe to use. Use 'list' command to see available probes + #[serde(alias = "probe")] + pub(crate) probe_selector: Option, + + /// The MCU Core to debug. Default is 0 + #[serde(default)] + pub(crate) core_index: usize, + + /// The target to be selected. + pub(crate) chip: Option, + + /// Protocol to use for target connection + #[serde(rename = "wire_protocol")] + pub(crate) protocol: Option, + + /// Protocol speed in kHz + pub(crate) speed: Option, + + /// Assert target's reset during connect + #[serde(default)] + pub(crate) connect_under_reset: bool, + + /// Allow the chip to be fully erased + #[serde(default)] + pub(crate) allow_erase_all: bool, + + /// IP port number to listen for incoming DAP connections, e.g. "50000" + pub(crate) port: Option, + + /// Flash the target before debugging + #[serde(default)] + pub(crate) flashing_enabled: bool, + + /// Reset the target after flashing + #[serde(default)] + pub(crate) reset_after_flashing: bool, + + /// Halt the target after reset + #[serde(default)] + pub(crate) halt_after_reset: bool, + + /// Do a full chip erase, versus page-by-page erase + #[serde(default)] + pub(crate) full_chip_erase: bool, + + /// Restore erased bytes that will not be rewritten from ELF + #[serde(default)] + pub(crate) restore_unwritten_bytes: bool, + + /// Level of information to be logged to the debugger console (Error, Info or Debug ) + #[serde(default = "default_console_log")] + pub(crate) console_log_level: Option, + + #[serde(flatten)] + pub(crate) rtt: rtt::RttConfig, +} + +impl SessionConfig { + /// Validate the new cwd, or else set it from the environment. + pub(crate) fn validate_and_update_cwd(&mut self, new_cwd: Option) { + self.cwd = match new_cwd { + Some(temp_path) => { + if temp_path.is_dir() { + Some(temp_path) + } else if let Ok(current_dir) = current_dir() { + Some(current_dir) + } else { + log::error!("Cannot use current working directory. Please check existence and permissions."); + None + } + } + None => { + if let Ok(current_dir) = current_dir() { + Some(current_dir) + } else { + log::error!("Cannot use current working directory. Please check existence and permissions."); + None + } + } + }; + } + + /// If the path to the program to be debugged is relative, we join if with the cwd. + pub(crate) fn qualify_and_update_os_file_path( + &mut self, + os_file_to_validate: Option, + ) -> Result { + match os_file_to_validate { + Some(temp_path) => { + let mut new_path = PathBuf::new(); + if temp_path.is_relative() { + if let Some(cwd_path) = self.cwd.clone() { + new_path.push(cwd_path); + } else { + return Err(DebuggerError::Other(anyhow!( + "Invalid value {:?} for `cwd`", + self.cwd + ))); + } + } + new_path.push(temp_path); + Ok(new_path) + } + None => Err(DebuggerError::Other(anyhow!("Missing value for file."))), + } + } +} + +fn default_console_log() -> Option { + Some(ConsoleLog::Error) +} + +/// The level of information to be logged to the debugger console. The DAP Client will set appropriate RUST_LOG env for 'launch' configurations, and will pass the rust log output to the client debug console. +#[derive(Copy, Clone, PartialEq, Debug, serde::Serialize, serde::Deserialize)] +pub enum ConsoleLog { + Error, + Warn, + Info, + Debug, + Trace, +} + +impl std::str::FromStr for ConsoleLog { + type Err = String; + + fn from_str(s: &str) -> Result { + match &s.to_ascii_lowercase()[..] { + "error" => Ok(ConsoleLog::Error), + "warn" => Ok(ConsoleLog::Error), + "info" => Ok(ConsoleLog::Info), + "debug" => Ok(ConsoleLog::Debug), + "trace" => Ok(ConsoleLog::Trace), + _ => Err(format!( + "'{}' is not a valid console log level. Choose from [error, warn, info, debug, or trace].", + s + )), + } + } +} diff --git a/debugger/src/debugger/core_data.rs b/debugger/src/debugger/core_data.rs new file mode 100644 index 0000000000..7a7fa5519c --- /dev/null +++ b/debugger/src/debugger/core_data.rs @@ -0,0 +1,125 @@ +use super::session_data; +use crate::{ + debug_adapter::{dap_adapter::DebugAdapter, protocol::ProtocolAdapter}, + debugger::debug_rtt, + DebuggerError, +}; +use anyhow::Result; +use capstone::Capstone; +use probe_rs::{debug::DebugInfo, Core}; +use probe_rs_cli_util::rtt; + +/// [CoreData] provides handles to various data structures required to debug a single instance of a core. The actual state is stored in [SessionData]. +/// +/// Usage: To get access to this structure please use the [SessionData::attach_core] method. Please keep access/locks to this to a minumum duration. +pub struct CoreData<'p> { + pub(crate) target_core: Core<'p>, + pub(crate) target_name: String, + pub(crate) debug_info: &'p DebugInfo, + pub(crate) stack_frames: &'p mut Vec, + pub(crate) capstone: &'p Capstone, + pub(crate) breakpoints: &'p mut Vec, + pub(crate) rtt_connection: &'p mut Option, +} + +impl<'p> CoreData<'p> { + /// Search available [StackFrame]'s for the given `id` + pub(crate) fn get_stackframe(&'p self, id: i64) -> Option<&'p probe_rs::debug::StackFrame> { + self.stack_frames + .iter() + .find(|stack_frame| stack_frame.id == id) + } + + /// Confirm RTT initialization on the target, and use the RTT channel configurations to initialize the output windows on the DAP Client. + pub fn attach_to_rtt( + &mut self, + debug_adapter: &mut DebugAdapter

, + target_memory_map: &[probe_rs::config::MemoryRegion], + program_binary: &std::path::Path, + rtt_config: &rtt::RttConfig, + ) -> Result<()> { + let mut debugger_rtt_channels: Vec = vec![]; + match rtt::attach_to_rtt( + &mut self.target_core, + target_memory_map, + program_binary, + rtt_config, + ) { + Ok(target_rtt) => { + for any_channel in target_rtt.active_channels.iter() { + if let Some(up_channel) = &any_channel.up_channel { + debugger_rtt_channels.push(debug_rtt::DebuggerRttChannel { + channel_number: up_channel.number(), + // This value will eventually be set to true by a VSCode client request "rttWindowOpened" + has_client_window: false, + }); + debug_adapter.rtt_window( + up_channel.number(), + any_channel.channel_name.clone(), + any_channel.data_format, + ); + } + } + *self.rtt_connection = Some(debug_rtt::RttConnection { + target_rtt, + debugger_rtt_channels, + }); + } + Err(_error) => { + log::warn!("Failed to initalize RTT. Will try again on the next request... "); + } + }; + Ok(()) + } + + /// Set a single breakpoint in target configuration as well as [`CoreData::breakpoints`] + pub(crate) fn set_breakpoint( + &mut self, + address: u32, + breakpoint_type: session_data::BreakpointType, + ) -> Result<(), DebuggerError> { + self.target_core + .set_hw_breakpoint(address) + .map_err(DebuggerError::ProbeRs)?; + self.breakpoints.push(session_data::ActiveBreakpoint { + breakpoint_type, + breakpoint_address: address, + }); + Ok(()) + } + + /// Clear a single breakpoint from target configuration as well as [`CoreData::breakpoints`] + pub(crate) fn clear_breakpoint(&mut self, address: u32) -> Result<()> { + self.target_core + .clear_hw_breakpoint(address) + .map_err(DebuggerError::ProbeRs)?; + let mut breakpoint_position: Option = None; + for (position, active_breakpoint) in self.breakpoints.iter().enumerate() { + if active_breakpoint.breakpoint_address == address { + breakpoint_position = Some(position); + break; + } + } + if let Some(breakpoint_position) = breakpoint_position { + self.breakpoints.remove(breakpoint_position as usize); + } + Ok(()) + } + + /// Clear all breakpoints of a specified [`BreakpointType`]. Affects target configuration as well as [`CoreData::breakpoints`] + pub(crate) fn clear_breakpoints( + &mut self, + breakpoint_type: session_data::BreakpointType, + ) -> Result<()> { + let target_breakpoints = self + .breakpoints + .iter() + .filter(|breakpoint| breakpoint.breakpoint_type == breakpoint_type) + .map(|breakpoint| breakpoint.breakpoint_address) + .collect::>(); + for breakpoint in target_breakpoints { + self.clear_breakpoint(breakpoint).ok(); + } + Ok(()) + } +} diff --git a/debugger/src/debugger.rs b/debugger/src/debugger/debug_entry.rs similarity index 63% rename from debugger/src/debugger.rs rename to debugger/src/debugger/debug_entry.rs index 9a0db5a8bc..f14bb9a264 100644 --- a/debugger/src/debugger.rs +++ b/debugger/src/debugger/debug_entry.rs @@ -1,79 +1,28 @@ -use crate::dap_types::*; -use crate::debug_adapter::*; -use crate::protocol::{DapAdapter, ProtocolAdapter}; - -use crate::DebuggerError; -use anyhow::{anyhow, Context, Result}; -use capstone::{ - arch::arm::ArchMode as armArchMode, arch::riscv::ArchMode as riscvArchMode, prelude::*, - Capstone, Endian, +use super::session_data; +use crate::{ + debug_adapter::{ + dap_adapter::*, + dap_types::*, + protocol::{DapAdapter, ProtocolAdapter}, + }, + debugger::configuration::{self, ConsoleLog}, + DebuggerError, }; -use probe_rs::config::TargetSelector; -use probe_rs::debug::DebugInfo; - -use probe_rs::flashing::download_file_with_options; -use probe_rs::flashing::DownloadOptions; -use probe_rs::flashing::FlashProgress; -use probe_rs::flashing::Format; -use probe_rs::ProbeCreationError; +use anyhow::{anyhow, Context, Result}; use probe_rs::{ - Core, CoreStatus, DebugProbeError, DebugProbeSelector, Permissions, Probe, Session, - WireProtocol, + flashing::{download_file_with_options, DownloadOptions, FlashProgress, Format}, + CoreStatus, Probe, }; -use probe_rs_cli_util::rtt; use serde::Deserialize; -use std::cell::RefCell; -use std::net::Ipv4Addr; -use std::net::TcpListener; -use std::ops::Mul; -use std::rc::Rc; use std::{ - env::{current_dir, set_current_dir}, - path::PathBuf, - str::FromStr, + cell::RefCell, + net::{Ipv4Addr, TcpListener}, + ops::Mul, + rc::Rc, thread, time::Duration, }; -fn default_console_log() -> Option { - Some(ConsoleLog::Error) -} - -fn parse_probe_selector(src: &str) -> Result { - match DebugProbeSelector::from_str(src) { - Ok(probe_selector) => Ok(probe_selector), - Err(error) => Err(error.to_string()), - } -} - -/// The level of information to be logged to the debugger console. The DAP Client will set appropriate RUST_LOG env for 'launch' configurations, and will pass the rust log output to the client debug console. -#[derive(Copy, Clone, PartialEq, Debug, serde::Serialize, serde::Deserialize)] -pub enum ConsoleLog { - Error, - Warn, - Info, - Debug, - Trace, -} - -impl std::str::FromStr for ConsoleLog { - type Err = String; - - fn from_str(s: &str) -> Result { - match &s.to_ascii_lowercase()[..] { - "error" => Ok(ConsoleLog::Error), - "warn" => Ok(ConsoleLog::Error), - "info" => Ok(ConsoleLog::Info), - "debug" => Ok(ConsoleLog::Debug), - "trace" => Ok(ConsoleLog::Trace), - _ => Err(format!( - "'{}' is not a valid console log level. Choose from [error, warn, info, debug, or trace].", - s - )), - } - } -} - #[derive(clap::Parser, Copy, Clone, Debug, Deserialize)] pub(crate) enum TargetSessionType { AttachRequest, @@ -95,550 +44,6 @@ impl std::str::FromStr for TargetSessionType { } } -/// Shared options for all commands which use a specific probe -#[derive(clap::Parser, Clone, Deserialize, Debug, Default)] -#[serde(rename_all = "camelCase")] -pub struct DebuggerOptions { - /// Path to the requested working directory for the debugger - #[clap(long, parse(from_os_str))] - pub(crate) cwd: Option, - - /// Binary to debug as a path. Relative to `cwd`, or fully qualified. - #[clap(long, parse(from_os_str))] - pub(crate) program_binary: Option, - - /// CMSIS-SVD file for the target. Relative to `cwd`, or fully qualified. - #[clap(long, parse(from_os_str))] - pub(crate) svd_file: Option, - - /// The number associated with the debug probe to use. Use 'list' command to see available probes - #[clap( - long = "probe", - parse(try_from_str = parse_probe_selector), - help = "Use this flag to select a specific probe in the list.\n\ - Use '--probe VID:PID' or '--probe VID:PID:Serial' if you have more than one probe with the same VID:PID." - )] - #[serde(alias = "probe")] - pub(crate) probe_selector: Option, - - /// The MCU Core to debug. Default is 0 - #[clap(long = "core-index", default_value_t)] - #[serde(default)] - pub(crate) core_index: usize, - - /// The target to be selected. - #[clap(short, long)] - pub(crate) chip: Option, - - /// Protocol to use for target connection - #[clap(short, long)] - #[serde(rename = "wire_protocol")] - pub(crate) protocol: Option, - - /// Protocol speed in kHz - #[clap(short, long)] - pub(crate) speed: Option, - - /// Assert target's reset during connect - #[clap(long)] - #[serde(default)] - pub(crate) connect_under_reset: bool, - - /// Allow the chip to be fully erased - #[structopt(long)] - #[serde(default)] - pub(crate) allow_erase_all: bool, - - /// IP port number to listen for incoming DAP connections, e.g. "50000" - #[clap(long)] - pub(crate) port: Option, - - /// Flash the target before debugging - #[clap(long)] - #[serde(default)] - pub(crate) flashing_enabled: bool, - - /// Reset the target after flashing - #[clap(long, required_if_eq("flashing-enabled", "true"))] - #[serde(default)] - pub(crate) reset_after_flashing: bool, - - /// Halt the target after reset - #[clap(long)] - #[serde(default)] - pub(crate) halt_after_reset: bool, - - /// Do a full chip erase, versus page-by-page erase - #[clap(long, required_if_eq("flashing-enabled", "true"))] - #[serde(default)] - pub(crate) full_chip_erase: bool, - - /// Restore erased bytes that will not be rewritten from ELF - #[clap(long, required_if_eq("flashing-enabled", "true"))] - #[serde(default)] - pub(crate) restore_unwritten_bytes: bool, - - /// Level of information to be logged to the debugger console (Error, Info or Debug ) - #[clap(long)] - #[serde(default = "default_console_log")] - pub(crate) console_log_level: Option, - - #[clap(flatten)] - #[serde(flatten)] - pub(crate) rtt: rtt::RttConfig, -} - -impl DebuggerOptions { - /// Validate the new cwd, or else set it from the environment. - pub(crate) fn validate_and_update_cwd(&mut self, new_cwd: Option) { - self.cwd = match new_cwd { - Some(temp_path) => { - if temp_path.is_dir() { - Some(temp_path) - } else if let Ok(current_dir) = current_dir() { - Some(current_dir) - } else { - log::error!("Cannot use current working directory. Please check existence and permissions."); - None - } - } - None => { - if let Ok(current_dir) = current_dir() { - Some(current_dir) - } else { - log::error!("Cannot use current working directory. Please check existence and permissions."); - None - } - } - }; - } - - /// If the path to the program to be debugged is relative, we join if with the cwd. - pub(crate) fn qualify_and_update_os_file_path( - &mut self, - os_file_to_validate: Option, - ) -> Result { - match os_file_to_validate { - Some(temp_path) => { - let mut new_path = PathBuf::new(); - if temp_path.is_relative() { - if let Some(cwd_path) = self.cwd.clone() { - new_path.push(cwd_path); - } else { - return Err(DebuggerError::Other(anyhow!( - "Invalid value {:?} for `cwd`", - self.cwd - ))); - } - } - new_path.push(temp_path); - Ok(new_path) - } - None => Err(DebuggerError::Other(anyhow!("Missing value for file."))), - } - } -} - -/// The supported breakpoint types -#[derive(Debug, PartialEq)] -pub enum BreakpointType { - InstructionBreakpoint, - SourceBreakpoint, -} - -/// Provide the storage and methods to handle various [`BreakPointType`] -#[derive(Debug)] -pub struct ActiveBreakpoint { - breakpoint_type: BreakpointType, - breakpoint_address: u32, -} - -/// DebugSession is designed to be similar to [probe_rs::Session], in as much that it provides handles to the [CoreData] instances for each of the available [probe_rs::Core] involved in the debug session. -/// To get access to the [CoreData] for a specific [Core], the -/// TODO: Adjust [DebuggerOptions] to allow multiple cores (and if appropriate, their binaries) to be specified. -pub struct DebugSession { - pub(crate) session: Session, - /// Provides ability to disassemble binary code. - pub(crate) capstone: Capstone, - /// [DebugSession] will manage one [DebugInfo] per [DebuggerOptions::program_binary] - pub(crate) debug_infos: Vec, - /// [DebugSession] will manage a `Vec` per [Core]. Each core's collection of StackFrames will be recreated whenever a stacktrace is performed, using the results of [DebugInfo::unwind] - pub(crate) stack_frames: Vec>, - /// [DebugSession] will manage a `Vec` per [Core]. Each core's collection of ActiveBreakpoint's will be managed on demand. - pub(crate) breakpoints: Vec>, - /// The control structures for handling RTT in this Core of the DebugSession. - pub(crate) rtt_connection: Option, -} - -impl DebugSession { - pub(crate) fn new(debugger_options: &DebuggerOptions) -> Result { - let mut target_probe = match debugger_options.probe_selector.clone() { - Some(selector) => Probe::open(selector.clone()).map_err(|e| match e { - DebugProbeError::ProbeCouldNotBeCreated(ProbeCreationError::NotFound) => { - DebuggerError::Other(anyhow!( - "Could not find the probe_selector specified as {:04x}:{:04x}:{:?}", - selector.vendor_id, - selector.product_id, - selector.serial_number - )) - } - other_error => DebuggerError::DebugProbe(other_error), - }), - None => { - // Only automatically select a probe if there is only a single probe detected. - let list = Probe::list_all(); - if list.len() > 1 { - return Err(DebuggerError::Other(anyhow!( - "Found multiple ({}) probes", - list.len() - ))); - } - - if let Some(info) = list.first() { - Probe::open(info).map_err(DebuggerError::DebugProbe) - } else { - return Err(DebuggerError::Other(anyhow!( - "No probes found. Please check your USB connections." - ))); - } - } - }?; - - let target_selector = match &debugger_options.chip { - Some(identifier) => identifier.into(), - None => TargetSelector::Auto, - }; - - // Set the protocol, if the user explicitly selected a protocol. Otherwise, use the default protocol of the probe. - if let Some(protocol) = debugger_options.protocol { - target_probe.select_protocol(protocol)?; - } - - // Set the speed. - if let Some(speed) = debugger_options.speed { - let actual_speed = target_probe.set_speed(speed)?; - if actual_speed != speed { - log::warn!( - "Protocol speed {} kHz not supported, actual speed is {} kHz", - speed, - actual_speed - ); - } - } - - let mut permissions = Permissions::new(); - if debugger_options.allow_erase_all { - permissions = permissions.allow_erase_all(); - } - - // Attach to the probe. - let target_session = if debugger_options.connect_under_reset { - target_probe.attach_under_reset(target_selector, permissions)? - } else { - target_probe - .attach(target_selector, permissions) - .map_err(|err| { - anyhow!( - "Error attaching to the probe: {:?}.\nTry the --connect-under-reset option", - err - ) - })? - }; - - // Create an instance of the [`capstone::Capstone`] for disassembly capabilities. - let capstone = match target_session.architecture() { - probe_rs::Architecture::Arm => Capstone::new() - .arm() - .mode(armArchMode::Thumb) - .endian(Endian::Little) - .build() - .map_err(|err| anyhow!("Error creating Capstone disassembler: {:?}", err))?, - probe_rs::Architecture::Riscv => Capstone::new() - .riscv() - .mode(riscvArchMode::RiscV32) - .endian(Endian::Little) - .build() - .map_err(|err| anyhow!("Error creating Capstone disassembler: {:?}", err))?, - }; - - // Change the current working directory if `debugger_options.cwd` is `Some(T)`. - if let Some(new_cwd) = debugger_options.cwd.clone() { - set_current_dir(new_cwd.as_path()).map_err(|err| { - anyhow!( - "Failed to set current working directory to: {:?}, {:?}", - new_cwd, - err - ) - })?; - }; - - // TODO: We currently only allow a single core & binary to be specified in [DebuggerOptions]. When this is extended to support multicore, the following should initialize [DebugInfo] and [VariableCache] for each available core. - // Configure the [DebugInfo]. - let debug_infos = vec![ - if let Some(binary_path) = &debugger_options.program_binary { - DebugInfo::from_file(binary_path) - .map_err(|error| DebuggerError::Other(anyhow!(error)))? - } else { - return Err(anyhow!( - "Please provide a valid `program_binary` for this debug session" - ) - .into()); - }, - ]; - - // Configure the [VariableCache]. - let stack_frames = target_session.list_cores() - .iter() - .map(|(core_id, core_type)| { - log::debug!( - "Preparing stack frame variable cache for DebugSession and CoreData for core #{} of type: {:?}", - core_id, - core_type - ); - Vec::::new() - }).collect(); - - // Prepare the breakpoint cache - let breakpoints = target_session.list_cores() - .iter() - .map(|(core_id, core_type)| { - log::debug!( - "Preparing breakpoint cache for DebugSession and CoreData for core #{} of type: {:?}", - core_id, - core_type - ); - Vec::::new() - }).collect(); - - Ok(DebugSession { - session: target_session, - capstone, - debug_infos, - stack_frames, - breakpoints, - rtt_connection: None, - }) - } - - pub fn attach_core(&mut self, core_index: usize) -> Result { - let target_name = self.session.target().name.clone(); - // Do a 'light weight'(just get references to existing data structures) attach to the core and return relevant debug data. - match self.session.core(core_index) { - Ok(target_core) => Ok(CoreData { - target_core, - target_name: format!("{}-{}", core_index, target_name), - debug_info: self.debug_infos.get(core_index).ok_or_else(|| { - DebuggerError::Other(anyhow!( - "No available `DebugInfo` for core # {}", - core_index - )) - })?, - stack_frames: self.stack_frames.get_mut(core_index).ok_or_else(|| { - DebuggerError::Other(anyhow!( - "StackFrame cache was not correctly configured for core # {}", - core_index - )) - })?, - capstone: &self.capstone, - breakpoints: self.breakpoints.get_mut(core_index).ok_or_else(|| { - DebuggerError::Other(anyhow!( - "ActiveBreakpoint cache was not correctly configured for core # {}", - core_index - )) - })?, - rtt_connection: &mut self.rtt_connection, - }), - Err(_) => Err(DebuggerError::UnableToOpenProbe(Some( - "No core at the specified index.", - ))), - } - } -} - -/// Manage the active RTT target for a specific DebugSession, as well as provide methods to reliably move RTT from target, through the debug_adapter, to the client. -pub(crate) struct RttConnection { - /// The connection to RTT on the target - target_rtt: rtt::RttActiveTarget, - /// Some status fields and methods to ensure continuity in flow of data from target to debugger to client. - debugger_rtt_channels: Vec, -} - -impl RttConnection { - /// Polls all the available channels for data and transmits data to the client. - /// If at least one channel had data, then return a `true` status. - pub fn process_rtt_data( - &mut self, - debug_adapter: &mut DebugAdapter

, - target_core: &mut Core, - ) -> bool { - let mut at_least_one_channel_had_data = false; - for debugger_rtt_channel in self.debugger_rtt_channels.iter_mut() { - if debugger_rtt_channel.poll_rtt_data(target_core, debug_adapter, &mut self.target_rtt) - { - at_least_one_channel_had_data = true; - } - } - at_least_one_channel_had_data - } -} - -pub(crate) struct DebuggerRttChannel { - pub(crate) channel_number: usize, - // We will not poll target RTT channels until we have confirmation from the client that the output window has been opened. - pub(crate) has_client_window: bool, -} -impl DebuggerRttChannel { - /// Poll and retrieve data from the target, and send it to the client, depending on the state of `hasClientWindow`. - /// Doing this selectively ensures that we don't pull data from target buffers until we have an output window, and also helps us drain buffers after the target has entered a `is_halted` state. - /// Errors will be reported back to the `debug_adapter`, and the return `bool` value indicates whether there was available data that was processed. - pub(crate) fn poll_rtt_data( - &mut self, - core: &mut Core, - debug_adapter: &mut DebugAdapter

, - rtt_target: &mut rtt::RttActiveTarget, - ) -> bool { - if self.has_client_window { - rtt_target - .active_channels - .iter_mut() - .find(|active_channel| { - if let Some(channel_number) = active_channel.number() { - channel_number == self.channel_number - } else { - false - } - }) - .and_then(|rtt_channel| { - rtt_channel.get_rtt_data(core, rtt_target.defmt_state.as_ref()) - }) - .and_then(|(channel_number, channel_data)| { - if debug_adapter - .rtt_output(channel_number.parse::().unwrap_or(0), channel_data) - { - Some(true) - } else { - None - } - }) - .is_some() - } else { - false - } - } -} - -/// [CoreData] provides handles to various data structures required to debug a single instance of a core. The actual state is stored in [SessionData]. -/// -/// Usage: To get access to this structure please use the [DebugSession::attach_core] method. Please keep access/locks to this to a minumum duration. -pub struct CoreData<'p> { - pub(crate) target_core: Core<'p>, - pub(crate) target_name: String, - pub(crate) debug_info: &'p DebugInfo, - pub(crate) stack_frames: &'p mut Vec, - pub(crate) capstone: &'p Capstone, - pub(crate) breakpoints: &'p mut Vec, - pub(crate) rtt_connection: &'p mut Option, -} - -impl<'p> CoreData<'p> { - /// Search available [StackFrame]'s for the given `id` - pub(crate) fn get_stackframe(&'p self, id: i64) -> Option<&'p probe_rs::debug::StackFrame> { - self.stack_frames - .iter() - .find(|stack_frame| stack_frame.id == id) - } - - /// Confirm RTT initialization on the target, and use the RTT channel configurations to initialize the output windows on the DAP Client. - pub fn attach_to_rtt( - &mut self, - debug_adapter: &mut DebugAdapter

, - target_memory_map: &[probe_rs::config::MemoryRegion], - program_binary: &std::path::Path, - rtt_config: &rtt::RttConfig, - ) -> Result<()> { - let mut debugger_rtt_channels: Vec = vec![]; - match rtt::attach_to_rtt( - &mut self.target_core, - target_memory_map, - program_binary, - rtt_config, - ) { - Ok(target_rtt) => { - for any_channel in target_rtt.active_channels.iter() { - if let Some(up_channel) = &any_channel.up_channel { - debugger_rtt_channels.push(DebuggerRttChannel { - channel_number: up_channel.number(), - // This value will eventually be set to true by a VSCode client request "rttWindowOpened" - has_client_window: false, - }); - debug_adapter.rtt_window( - up_channel.number(), - any_channel.channel_name.clone(), - any_channel.data_format, - ); - } - } - *self.rtt_connection = Some(RttConnection { - target_rtt, - debugger_rtt_channels, - }); - } - Err(_error) => { - log::warn!("Failed to initalize RTT. Will try again on the next request... "); - } - }; - Ok(()) - } - - /// Set a single breakpoint in target configuration as well as [`CoreData::breakpoints`] - pub(crate) fn set_breakpoint( - &mut self, - address: u32, - breakpoint_type: BreakpointType, - ) -> Result<(), DebuggerError> { - self.target_core - .set_hw_breakpoint(address) - .map_err(DebuggerError::ProbeRs)?; - self.breakpoints.push(ActiveBreakpoint { - breakpoint_type, - breakpoint_address: address, - }); - Ok(()) - } - - /// Clear a single breakpoint from target configuration as well as [`CoreData::breakpoints`] - pub(crate) fn clear_breakpoint(&mut self, address: u32) -> Result<()> { - self.target_core - .clear_hw_breakpoint(address) - .map_err(DebuggerError::ProbeRs)?; - let mut breakpoint_position: Option = None; - for (position, active_breakpoint) in self.breakpoints.iter().enumerate() { - if active_breakpoint.breakpoint_address == address { - breakpoint_position = Some(position); - break; - } - } - if let Some(breakpoint_position) = breakpoint_position { - self.breakpoints.remove(breakpoint_position as usize); - } - Ok(()) - } - - /// Clear all breakpoints of a specified [`BreakpointType`]. Affects target configuration as well as [`CoreData::breakpoints`] - pub(crate) fn clear_breakpoints(&mut self, breakpoint_type: BreakpointType) -> Result<()> { - let target_breakpoints = self - .breakpoints - .iter() - .filter(|breakpoint| breakpoint.breakpoint_type == breakpoint_type) - .map(|breakpoint| breakpoint.breakpoint_address) - .collect::>(); - for breakpoint in target_breakpoints { - self.clear_breakpoint(breakpoint).ok(); - } - Ok(()) - } -} - #[derive(Debug)] /// The `DebuggerStatus` is used to control how the Debugger::debug_session() decides if it should respond to DAP Client requests such as `Terminate`, `Disconnect`, and `Reset`, as well as how to repond to unrecoverable errors during a debug session interacting with a target session. pub(crate) enum DebuggerStatus { @@ -652,19 +57,22 @@ pub(crate) enum DebuggerStatus { /// - In this case, the management (start and stop) of the server process is the responsibility of the user. e.g. /// - `probe-rs-debug --debug --port ` : Uses TCP Sockets to the defined IP port number to service DAP requests. pub struct Debugger { - debugger_options: DebuggerOptions, + config: configuration::SessionConfig, } impl Debugger { - pub fn new(debugger_options: DebuggerOptions) -> Self { - // Define all the commands supported by the debugger. - // TODO: There is a lot of repetitive code here, and a great opportunity for macros. - Self { debugger_options } + pub fn new(port: Option) -> Self { + Self { + config: configuration::SessionConfig { + port, + ..Default::default() + }, + } } pub(crate) fn process_next_request( &mut self, - session_data: &mut DebugSession, + session_data: &mut session_data::SessionData, debug_adapter: &mut DebugAdapter

, ) -> Result { let request = debug_adapter.listen_for_request()?; @@ -683,7 +91,7 @@ impl Debugger { CoreStatus::Unknown => Ok(DebuggerStatus::ContinueSession), // Don't do anything until we know VSCode's startup sequence is complete, and changes this to either Halted or Running. CoreStatus::Halted(_) => { // Make sure the RTT buffers are drained. - match session_data.attach_core(self.debugger_options.core_index) { + match session_data.attach_core(self.config.core_index) { Ok(mut core_data) => { if let Some(rtt_active_target) = &mut core_data.rtt_connection { rtt_active_target.process_rtt_data( @@ -704,23 +112,22 @@ impl Debugger { } _other => { let mut received_rtt_data = false; - let mut core_data = - match session_data.attach_core(self.debugger_options.core_index) { - Ok(mut core_data) => { - // Use every opportunity to poll the RTT channels for data - if let Some(rtt_active_target) = &mut core_data.rtt_connection { - received_rtt_data = rtt_active_target.process_rtt_data( - debug_adapter, - &mut core_data.target_core, - ); - } - core_data - } - Err(error) => { - let _ = debug_adapter.send_error_response(&error)?; - return Err(error); + let mut core_data = match session_data.attach_core(self.config.core_index) { + Ok(mut core_data) => { + // Use every opportunity to poll the RTT channels for data + if let Some(rtt_active_target) = &mut core_data.rtt_connection { + received_rtt_data = rtt_active_target.process_rtt_data( + debug_adapter, + &mut core_data.target_core, + ); } - }; + core_data + } + Err(error) => { + let _ = debug_adapter.send_error_response(&error)?; + return Err(error); + } + }; // Check and update the core status. let new_status = match core_data.target_core.status() { @@ -790,8 +197,7 @@ impl Debugger { } Some(request) => { // First, attach to the core. - let mut core_data = match session_data.attach_core(self.debugger_options.core_index) - { + let mut core_data = match session_data.attach_core(self.config.core_index) { Ok(core_data) => core_data, Err(error) => { let failed_command = request.command.clone(); @@ -1114,17 +520,17 @@ impl Debugger { match get_arguments(&launch_attach_request) { Ok(arguments) => { if requested_target_session_type.is_some() { - self.debugger_options = DebuggerOptions { ..arguments }; + self.config = configuration::SessionConfig { ..arguments }; if matches!( requested_target_session_type, Some(TargetSessionType::AttachRequest) ) { // Since VSCode doesn't do field validation checks for relationships in launch.json request types, check it here. - if self.debugger_options.flashing_enabled - || self.debugger_options.reset_after_flashing - || self.debugger_options.halt_after_reset - || self.debugger_options.full_chip_erase - || self.debugger_options.restore_unwritten_bytes + if self.config.flashing_enabled + || self.config.reset_after_flashing + || self.config.halt_after_reset + || self.config.full_chip_erase + || self.config.restore_unwritten_bytes { debug_adapter.send_response::<()>( launch_attach_request, @@ -1138,17 +544,14 @@ impl Debugger { } } debug_adapter.set_console_log_level( - self.debugger_options - .console_log_level - .unwrap_or(ConsoleLog::Error), + self.config.console_log_level.unwrap_or(ConsoleLog::Error), ); // Update the `cwd` and `program_binary`. - self.debugger_options - .validate_and_update_cwd(self.debugger_options.cwd.clone()); + self.config.validate_and_update_cwd(self.config.cwd.clone()); // Update the `program_binary` and validate that the file exists. - self.debugger_options.program_binary = match self - .debugger_options - .qualify_and_update_os_file_path(self.debugger_options.program_binary.clone()) + self.config.program_binary = match self + .config + .qualify_and_update_os_file_path(self.config.program_binary.clone()) { Ok(program_binary) => { if !program_binary.is_file() { @@ -1180,9 +583,9 @@ impl Debugger { }; // Update the `svd_file` and validate that the file exists. // If there is a problem with this file, warn the user and continue with the session. - self.debugger_options.svd_file = match self - .debugger_options - .qualify_and_update_os_file_path(self.debugger_options.svd_file.clone()) + self.config.svd_file = match self + .config + .qualify_and_update_os_file_path(self.config.svd_file.clone()) { Ok(svd_file) => { if !svd_file.is_file() { @@ -1204,7 +607,10 @@ impl Debugger { debug_adapter.send_response::<()>(launch_attach_request, Ok(None))?; } Err(error) => { - let error_message = format!("Could not derive DebuggerOptions from request '{}', with arguments {:?}\n{:?} ", launch_attach_request.command, launch_attach_request.arguments, error); + let error_message = format!( + "Could not derive SessionConfig from request '{}', with arguments {:?}\n{:?} ", + launch_attach_request.command, launch_attach_request.arguments, error + ); debug_adapter.send_response::<()>( launch_attach_request, Err(DebuggerError::Other(anyhow!(error_message.clone()))), @@ -1214,19 +620,19 @@ impl Debugger { } }; - let mut session_data = match DebugSession::new(&self.debugger_options) { + let mut session_data = match session_data::SessionData::new(&self.config) { Ok(session_data) => session_data, Err(error) => { debug_adapter.send_error_response(&error)?; return Err(error); } }; - debug_adapter.halt_after_reset = self.debugger_options.halt_after_reset; + debug_adapter.halt_after_reset = self.config.halt_after_reset; // Do the flashing. { - if self.debugger_options.flashing_enabled { - let path_to_elf = match self.debugger_options.program_binary.clone() { + if self.config.flashing_enabled { + let path_to_elf = match self.config.program_binary.clone() { Some(program_binary) => program_binary, None => { let err = DebuggerError::Other(anyhow!( @@ -1244,9 +650,8 @@ impl Debugger { let progress_id = debug_adapter.start_progress("Flashing device", None).ok(); let mut download_options = DownloadOptions::default(); - download_options.keep_unwritten_bytes = - self.debugger_options.restore_unwritten_bytes; - download_options.do_chip_erase = self.debugger_options.full_chip_erase; + download_options.keep_unwritten_bytes = self.config.restore_unwritten_bytes; + download_options.do_chip_erase = self.config.full_chip_erase; let flash_result = { let rc_debug_adapter = Rc::new(RefCell::new(debug_adapter)); let rc_debug_adapter_clone = rc_debug_adapter.clone(); @@ -1457,10 +862,10 @@ impl Debugger { // This is the first attach to the requested core. If this one works, all subsequent ones will be no-op requests for a Core reference. Do NOT hold onto this reference for the duration of the session ... that is why this code is in a block of its own. { // First, attach to the core - let mut core_data = match session_data.attach_core(self.debugger_options.core_index) { + let mut core_data = match session_data.attach_core(self.config.core_index) { Ok(mut core_data) => { // Immediately after attaching, halt the core, so that we can finish initalization without bumping into user code. - // Depending on supplied `debugger_options`, the core will be restarted at the end of initialization in the `configuration_done` request. + // Depending on supplied `config`, the core will be restarted at the end of initialization in the `configuration_done` request. match halt_core(&mut core_data.target_core) { Ok(_) => {} Err(error) => { @@ -1476,8 +881,7 @@ impl Debugger { } }; - if self.debugger_options.flashing_enabled && self.debugger_options.reset_after_flashing - { + if self.config.flashing_enabled && self.config.reset_after_flashing { debug_adapter .restart(&mut core_data, None) .context("Failed to restart core")?; @@ -1503,21 +907,20 @@ impl Debugger { match self.process_next_request(&mut session_data, &mut debug_adapter) { Ok(DebuggerStatus::ContinueSession) => { // Validate and if necessary, initialize the RTT structure. - if self.debugger_options.rtt.enabled + if self.config.rtt.enabled && session_data.rtt_connection.is_none() && !(debug_adapter.last_known_status == CoreStatus::Unknown || debug_adapter.last_known_status.is_halted()) // Do not attempt this until we have processed the MSDAP request for "configurationDone" ... { let target_memory_map = session_data.session.target().memory_map.clone(); - let mut core_data = - match session_data.attach_core(self.debugger_options.core_index) { - Ok(core_data) => core_data, - Err(error) => { - debug_adapter.send_error_response(&error)?; - return Err(error); - } - }; + let mut core_data = match session_data.attach_core(self.config.core_index) { + Ok(core_data) => core_data, + Err(error) => { + debug_adapter.send_error_response(&error)?; + return Err(error); + } + }; log::info!("Attempting to initialize the RTT."); // RTT can only be initialized if the target application has been allowed to run to the point where it does the RTT initialization. // If the target halts before it processes this code, then this RTT intialization silently fails, and will try again later ... @@ -1527,8 +930,8 @@ impl Debugger { core_data.attach_to_rtt( &mut debug_adapter, &target_memory_map, - self.debugger_options.program_binary.as_ref().unwrap(), - &self.debugger_options.rtt, + self.config.program_binary.as_ref().unwrap(), + &self.config.rtt, )?; } } @@ -1587,16 +990,16 @@ pub fn list_supported_chips() -> Result<()> { Ok(()) } -pub fn debug(debugger_options: DebuggerOptions, vscode: bool) -> Result<()> { +pub fn debug(port: Option, vscode: bool) -> Result<()> { let program_name = clap::crate_name!(); - let mut debugger = Debugger::new(debugger_options); + let mut debugger = Debugger::new(port); println!( "{} CONSOLE: Starting as a DAP Protocol server", &program_name ); - match &debugger.debugger_options.port.clone() { + match &debugger.config.port.clone() { Some(port) => { let addr = std::net::SocketAddr::new( std::net::IpAddr::V4(Ipv4Addr::LOCALHOST), diff --git a/debugger/src/debugger/debug_rtt.rs b/debugger/src/debugger/debug_rtt.rs new file mode 100644 index 0000000000..a2b95bb0ee --- /dev/null +++ b/debugger/src/debugger/debug_rtt.rs @@ -0,0 +1,76 @@ +use crate::debug_adapter::{dap_adapter::*, protocol::ProtocolAdapter}; +use probe_rs::Core; +use probe_rs_cli_util::rtt; + +/// Manage the active RTT target for a specific SessionData, as well as provide methods to reliably move RTT from target, through the debug_adapter, to the client. +pub(crate) struct RttConnection { + /// The connection to RTT on the target + pub(crate) target_rtt: rtt::RttActiveTarget, + /// Some status fields and methods to ensure continuity in flow of data from target to debugger to client. + pub(crate) debugger_rtt_channels: Vec, +} + +impl RttConnection { + /// Polls all the available channels for data and transmits data to the client. + /// If at least one channel had data, then return a `true` status. + pub fn process_rtt_data( + &mut self, + debug_adapter: &mut DebugAdapter

, + target_core: &mut Core, + ) -> bool { + let mut at_least_one_channel_had_data = false; + for debugger_rtt_channel in self.debugger_rtt_channels.iter_mut() { + if debugger_rtt_channel.poll_rtt_data(target_core, debug_adapter, &mut self.target_rtt) + { + at_least_one_channel_had_data = true; + } + } + at_least_one_channel_had_data + } +} + +pub(crate) struct DebuggerRttChannel { + pub(crate) channel_number: usize, + // We will not poll target RTT channels until we have confirmation from the client that the output window has been opened. + pub(crate) has_client_window: bool, +} + +impl DebuggerRttChannel { + /// Poll and retrieve data from the target, and send it to the client, depending on the state of `hasClientWindow`. + /// Doing this selectively ensures that we don't pull data from target buffers until we have an output window, and also helps us drain buffers after the target has entered a `is_halted` state. + /// Errors will be reported back to the `debug_adapter`, and the return `bool` value indicates whether there was available data that was processed. + pub(crate) fn poll_rtt_data( + &mut self, + core: &mut Core, + debug_adapter: &mut DebugAdapter

, + rtt_target: &mut rtt::RttActiveTarget, + ) -> bool { + if self.has_client_window { + rtt_target + .active_channels + .iter_mut() + .find(|active_channel| { + if let Some(channel_number) = active_channel.number() { + channel_number == self.channel_number + } else { + false + } + }) + .and_then(|rtt_channel| { + rtt_channel.get_rtt_data(core, rtt_target.defmt_state.as_ref()) + }) + .and_then(|(channel_number, channel_data)| { + if debug_adapter + .rtt_output(channel_number.parse::().unwrap_or(0), channel_data) + { + Some(true) + } else { + None + } + }) + .is_some() + } else { + false + } + } +} diff --git a/debugger/src/debugger/mod.rs b/debugger/src/debugger/mod.rs new file mode 100644 index 0000000000..b0bd1d6c9d --- /dev/null +++ b/debugger/src/debugger/mod.rs @@ -0,0 +1,10 @@ +/// All the shared options that control the behaviour of the debugger. +pub(crate) mod configuration; +/// The data structures borrowed from the [`SessionData`], that applies to a specific core. +pub(crate) mod core_data; +/// This is where the primary processing for the debugger is driven from. +pub(crate) mod debug_entry; +/// The debugger support for rtt. +pub(crate) mod debug_rtt; +/// The data structures needed to keep track of a [`SessionData`]. +pub(crate) mod session_data; diff --git a/debugger/src/debugger/session_data.rs b/debugger/src/debugger/session_data.rs new file mode 100644 index 0000000000..cb4dfe8826 --- /dev/null +++ b/debugger/src/debugger/session_data.rs @@ -0,0 +1,231 @@ +use super::configuration; +use super::core_data::CoreData; +use crate::debugger::debug_rtt; +use crate::DebuggerError; +use anyhow::{anyhow, Result}; +use capstone::Endian; +use capstone::{ + arch::arm::ArchMode as armArchMode, arch::riscv::ArchMode as riscvArchMode, prelude::*, + Capstone, +}; +use probe_rs::config::TargetSelector; +use probe_rs::debug::DebugInfo; +use probe_rs::DebugProbeError; +use probe_rs::Permissions; +use probe_rs::Probe; +use probe_rs::ProbeCreationError; +use probe_rs::Session; +use std::env::set_current_dir; + +/// The supported breakpoint types +#[derive(Debug, PartialEq)] +pub enum BreakpointType { + InstructionBreakpoint, + SourceBreakpoint, +} + +/// Provide the storage and methods to handle various [`BreakPointType`] +#[derive(Debug)] +pub struct ActiveBreakpoint { + pub(crate) breakpoint_type: BreakpointType, + pub(crate) breakpoint_address: u32, +} + +/// SessionData is designed to be similar to [probe_rs::Session], in as much that it provides handles to the [CoreData] instances for each of the available [probe_rs::Core] involved in the debug session. +/// To get access to the [CoreData] for a specific [Core], the +/// TODO: Adjust [SessionConfig] to allow multiple cores (and if appropriate, their binaries) to be specified. +pub struct SessionData { + pub(crate) session: Session, + /// Provides ability to disassemble binary code. + pub(crate) capstone: Capstone, + /// [SessionData] will manage one [DebugInfo] per [SessionConfig::program_binary] + pub(crate) debug_infos: Vec, + /// [SessionData] will manage a `Vec` per [Core]. Each core's collection of StackFrames will be recreated whenever a stacktrace is performed, using the results of [DebugInfo::unwind] + pub(crate) stack_frames: Vec>, + /// [SessionData] will manage a `Vec` per [Core]. Each core's collection of ActiveBreakpoint's will be managed on demand. + pub(crate) breakpoints: Vec>, + /// The control structures for handling RTT in this Core of the SessionData. + pub(crate) rtt_connection: Option, +} + +impl SessionData { + pub(crate) fn new(config: &configuration::SessionConfig) -> Result { + let mut target_probe = match config.probe_selector.clone() { + Some(selector) => Probe::open(selector.clone()).map_err(|e| match e { + DebugProbeError::ProbeCouldNotBeCreated(ProbeCreationError::NotFound) => { + DebuggerError::Other(anyhow!( + "Could not find the probe_selector specified as {:04x}:{:04x}:{:?}", + selector.vendor_id, + selector.product_id, + selector.serial_number + )) + } + other_error => DebuggerError::DebugProbe(other_error), + }), + None => { + // Only automatically select a probe if there is only a single probe detected. + let list = Probe::list_all(); + if list.len() > 1 { + return Err(DebuggerError::Other(anyhow!( + "Found multiple ({}) probes", + list.len() + ))); + } + + if let Some(info) = list.first() { + Probe::open(info).map_err(DebuggerError::DebugProbe) + } else { + return Err(DebuggerError::Other(anyhow!( + "No probes found. Please check your USB connections." + ))); + } + } + }?; + + let target_selector = match &config.chip { + Some(identifier) => identifier.into(), + None => TargetSelector::Auto, + }; + + // Set the protocol, if the user explicitly selected a protocol. Otherwise, use the default protocol of the probe. + if let Some(protocol) = config.protocol { + target_probe.select_protocol(protocol)?; + } + + // Set the speed. + if let Some(speed) = config.speed { + let actual_speed = target_probe.set_speed(speed)?; + if actual_speed != speed { + log::warn!( + "Protocol speed {} kHz not supported, actual speed is {} kHz", + speed, + actual_speed + ); + } + } + + let mut permissions = Permissions::new(); + if config.allow_erase_all { + permissions = permissions.allow_erase_all(); + } + + // Attach to the probe. + let target_session = if config.connect_under_reset { + target_probe.attach_under_reset(target_selector, permissions)? + } else { + target_probe + .attach(target_selector, permissions) + .map_err(|err| { + anyhow!( + "Error attaching to the probe: {:?}.\nTry the --connect-under-reset option", + err + ) + })? + }; + + // Create an instance of the [`capstone::Capstone`] for disassembly capabilities. + let capstone = match target_session.architecture() { + probe_rs::Architecture::Arm => Capstone::new() + .arm() + .mode(armArchMode::Thumb) + .endian(Endian::Little) + .build() + .map_err(|err| anyhow!("Error creating Capstone disassembler: {:?}", err))?, + probe_rs::Architecture::Riscv => Capstone::new() + .riscv() + .mode(riscvArchMode::RiscV32) + .endian(Endian::Little) + .build() + .map_err(|err| anyhow!("Error creating Capstone disassembler: {:?}", err))?, + }; + + // Change the current working directory if `config.cwd` is `Some(T)`. + if let Some(new_cwd) = config.cwd.clone() { + set_current_dir(new_cwd.as_path()).map_err(|err| { + anyhow!( + "Failed to set current working directory to: {:?}, {:?}", + new_cwd, + err + ) + })?; + }; + + // TODO: We currently only allow a single core & binary to be specified in [SessionConfig]. When this is extended to support multicore, the following should initialize [DebugInfo] and [VariableCache] for each available core. + // Configure the [DebugInfo]. + let debug_infos = vec![if let Some(binary_path) = &config.program_binary { + DebugInfo::from_file(binary_path) + .map_err(|error| DebuggerError::Other(anyhow!(error)))? + } else { + return Err( + anyhow!("Please provide a valid `program_binary` for this debug session").into(), + ); + }]; + + // Configure the [VariableCache]. + let stack_frames = target_session.list_cores() + .iter() + .map(|(core_id, core_type)| { + log::debug!( + "Preparing stack frame variable cache for SessionData and CoreData for core #{} of type: {:?}", + core_id, + core_type + ); + Vec::::new() + }).collect(); + + // Prepare the breakpoint cache + let breakpoints = target_session.list_cores() + .iter() + .map(|(core_id, core_type)| { + log::debug!( + "Preparing breakpoint cache for SessionData and CoreData for core #{} of type: {:?}", + core_id, + core_type + ); + Vec::::new() + }).collect(); + + Ok(SessionData { + session: target_session, + capstone, + debug_infos, + stack_frames, + breakpoints, + rtt_connection: None, + }) + } + + pub fn attach_core(&mut self, core_index: usize) -> Result { + let target_name = self.session.target().name.clone(); + // Do a 'light weight'(just get references to existing data structures) attach to the core and return relevant debug data. + match self.session.core(core_index) { + Ok(target_core) => Ok(CoreData { + target_core, + target_name: format!("{}-{}", core_index, target_name), + debug_info: self.debug_infos.get(core_index).ok_or_else(|| { + DebuggerError::Other(anyhow!( + "No available `DebugInfo` for core # {}", + core_index + )) + })?, + stack_frames: self.stack_frames.get_mut(core_index).ok_or_else(|| { + DebuggerError::Other(anyhow!( + "StackFrame cache was not correctly configured for core # {}", + core_index + )) + })?, + capstone: &self.capstone, + breakpoints: self.breakpoints.get_mut(core_index).ok_or_else(|| { + DebuggerError::Other(anyhow!( + "ActiveBreakpoint cache was not correctly configured for core # {}", + core_index + )) + })?, + rtt_connection: &mut self.rtt_connection, + }), + Err(_) => Err(DebuggerError::UnableToOpenProbe(Some( + "No core at the specified index.", + ))), + } + } +} diff --git a/debugger/src/main.rs b/debugger/src/main.rs index 62850dcced..0814734449 100644 --- a/debugger/src/main.rs +++ b/debugger/src/main.rs @@ -1,17 +1,15 @@ // Bad things happen to the VSCode debug extenison and debug_adapter if we panic at the wrong time. #![warn(clippy::unwrap_used, clippy::panic, clippy::expect_used)] // Uses Schemafy to generate DAP types from Json -mod dap_types; mod debug_adapter; mod debugger; -mod protocol; use anyhow::Result; use clap::{crate_authors, crate_description, crate_name, crate_version, Parser}; -use debugger::{debug, list_connected_devices, list_supported_chips, DebuggerOptions}; -use probe_rs::architecture::arm::ap::AccessPortError; -use probe_rs::flashing::FileDownloadError; -use probe_rs::{DebugProbeError, Error}; +use debugger::debug_entry::{debug, list_connected_devices, list_supported_chips}; +use probe_rs::{ + architecture::arm::ap::AccessPortError, flashing::FileDownloadError, DebugProbeError, Error, +}; #[derive(Debug, thiserror::Error)] pub enum DebuggerError { @@ -62,6 +60,8 @@ pub enum DebuggerError { author = crate_authors!(), version = crate_version!() )] + +/// There are only 3 command line options for the debugger. enum CliCommands { /// List all connected debug probes List {}, @@ -71,8 +71,9 @@ enum CliCommands { /// Open target in debug mode and accept debug commands. /// This only works as a [protocol::DapAdapter] and uses DAP Protocol debug commands (enables connections from clients such as Microsoft Visual Studio Code). Debug { - #[clap(flatten)] - debugger_options: DebuggerOptions, + /// IP port number to listen for incoming DAP connections, e.g. "50000" + #[clap(long)] + port: Option, /// The debug adapter processed was launched by VSCode, and should terminate itself at the end of every debug session (when receiving `Disconnect` or `Terminate` Request from VSCode). The "false"(default) state of this option implies that the process was launched (and will be managed) by the user. #[clap(long, hide = true)] @@ -90,10 +91,7 @@ fn main() -> Result<()> { match matches { CliCommands::List {} => list_connected_devices()?, CliCommands::ListChips {} => list_supported_chips()?, - CliCommands::Debug { - debugger_options, - vscode, - } => debug(debugger_options, vscode)?, + CliCommands::Debug { port, vscode } => debug(port, vscode)?, } Ok(()) } From 455dfd5c73b27e520fd6cedb1d4831bd7a90f41f Mon Sep 17 00:00:00 2001 From: JackN Date: Fri, 18 Mar 2022 15:02:29 -0400 Subject: [PATCH 03/29] broken: split debugger_options for multi-core --- debugger/src/debugger/configuration.rs | 113 +++++++++++++++---------- debugger/src/debugger/core_data.rs | 1 + debugger/src/debugger/debug_entry.rs | 23 ++--- debugger/src/debugger/session_data.rs | 44 +++++++--- 4 files changed, 112 insertions(+), 69 deletions(-) diff --git a/debugger/src/debugger/configuration.rs b/debugger/src/debugger/configuration.rs index 86272c0679..84414aa07b 100644 --- a/debugger/src/debugger/configuration.rs +++ b/debugger/src/debugger/configuration.rs @@ -3,76 +3,49 @@ use anyhow::{anyhow, Result}; use probe_rs::{DebugProbeSelector, WireProtocol}; use probe_rs_cli_util::rtt; use serde::Deserialize; -use std::{env::current_dir, path::PathBuf, str::FromStr}; +use std::{env::current_dir, path::PathBuf}; /// Shared options for all session level configuration. #[derive(Clone, Deserialize, Debug, Default)] #[serde(rename_all = "camelCase")] pub struct SessionConfig { - /// Path to the requested working directory for the debugger - pub(crate) cwd: Option, + /// IP port number to listen for incoming DAP connections, e.g. "50000" + pub(crate) port: Option, - /// Binary to debug as a path. Relative to `cwd`, or fully qualified. - pub(crate) program_binary: Option, + /// Level of information to be logged to the debugger console (Error, Info or Debug ) + #[serde(default = "default_console_log")] + pub(crate) console_log_level: Option, - /// CMSIS-SVD file for the target. Relative to `cwd`, or fully qualified. - pub(crate) svd_file: Option, + /// Path to the requested working directory for the debugger + pub(crate) cwd: Option, /// The number associated with the debug probe to use. Use 'list' command to see available probes #[serde(alias = "probe")] pub(crate) probe_selector: Option, - /// The MCU Core to debug. Default is 0 + /// Assert target's reset during connect #[serde(default)] - pub(crate) core_index: usize, + pub(crate) connect_under_reset: bool, - /// The target to be selected. - pub(crate) chip: Option, + /// Protocol speed in kHz + pub(crate) speed: Option, /// Protocol to use for target connection #[serde(rename = "wire_protocol")] pub(crate) protocol: Option, - /// Protocol speed in kHz - pub(crate) speed: Option, - - /// Assert target's reset during connect - #[serde(default)] - pub(crate) connect_under_reset: bool, - - /// Allow the chip to be fully erased - #[serde(default)] + ///Allow the session to erase all memory of the chip or reset it to factory default. pub(crate) allow_erase_all: bool, - /// IP port number to listen for incoming DAP connections, e.g. "50000" - pub(crate) port: Option, - - /// Flash the target before debugging - #[serde(default)] - pub(crate) flashing_enabled: bool, - - /// Reset the target after flashing - #[serde(default)] - pub(crate) reset_after_flashing: bool, - - /// Halt the target after reset - #[serde(default)] - pub(crate) halt_after_reset: bool, - - /// Do a full chip erase, versus page-by-page erase - #[serde(default)] - pub(crate) full_chip_erase: bool, - - /// Restore erased bytes that will not be rewritten from ELF - #[serde(default)] - pub(crate) restore_unwritten_bytes: bool, - - /// Level of information to be logged to the debugger console (Error, Info or Debug ) - #[serde(default = "default_console_log")] - pub(crate) console_log_level: Option, + /// Flashing configuration + #[serde(flatten)] + pub(crate) flashing_config: FlashingConfig, + /// Every core on the target has certain configuration. + /// + /// NOTE: Although we allow specifying multiple core configurations, this is a work in progress, and probe-rs-debugger currently only supports debugging a single core. #[serde(flatten)] - pub(crate) rtt: rtt::RttConfig, + pub(crate) core_configs: Vec, } impl SessionConfig { @@ -126,6 +99,52 @@ impl SessionConfig { } } +/// Configuration options to control flashing. +#[derive(Clone, Deserialize, Debug, Default)] +#[serde(rename_all = "camelCase")] +pub struct FlashingConfig { + /// Flash the target before debugging + #[serde(default)] + pub(crate) flashing_enabled: bool, + + /// Reset the target after flashing + #[serde(default)] + pub(crate) reset_after_flashing: bool, + + /// Halt the target after reset + #[serde(default)] + pub(crate) halt_after_reset: bool, + + /// Do a full chip erase, versus page-by-page erase + #[serde(default)] + pub(crate) full_chip_erase: bool, + + /// Restore erased bytes that will not be rewritten from ELF + #[serde(default)] + pub(crate) restore_unwritten_bytes: bool, +} + +/// Configuration options for all core level configuration. +#[derive(Clone, Deserialize, Debug, Default)] +#[serde(rename_all = "camelCase")] +pub struct CoreConfig { + /// The MCU Core to debug. Default is 0 + #[serde(default)] + pub(crate) core_index: usize, + + /// The target to be selected. + pub(crate) chip: Option, + + /// Binary to debug as a path. Relative to `cwd`, or fully qualified. + pub(crate) program_binary: Option, + + /// CMSIS-SVD file for the target. Relative to `cwd`, or fully qualified. + pub(crate) svd_file: Option, + + #[serde(flatten)] + pub(crate) rtt_config: rtt::RttConfig, +} + fn default_console_log() -> Option { Some(ConsoleLog::Error) } diff --git a/debugger/src/debugger/core_data.rs b/debugger/src/debugger/core_data.rs index 7a7fa5519c..adc0504c28 100644 --- a/debugger/src/debugger/core_data.rs +++ b/debugger/src/debugger/core_data.rs @@ -16,6 +16,7 @@ pub struct CoreData<'p> { pub(crate) target_core: Core<'p>, pub(crate) target_name: String, pub(crate) debug_info: &'p DebugInfo, + pub(crate) peripherals: &'p DebugInfo, pub(crate) stack_frames: &'p mut Vec, pub(crate) capstone: &'p Capstone, pub(crate) breakpoints: &'p mut Vec, diff --git a/debugger/src/debugger/debug_entry.rs b/debugger/src/debugger/debug_entry.rs index f14bb9a264..1e7fa56d91 100644 --- a/debugger/src/debugger/debug_entry.rs +++ b/debugger/src/debugger/debug_entry.rs @@ -526,11 +526,11 @@ impl Debugger { Some(TargetSessionType::AttachRequest) ) { // Since VSCode doesn't do field validation checks for relationships in launch.json request types, check it here. - if self.config.flashing_enabled - || self.config.reset_after_flashing - || self.config.halt_after_reset - || self.config.full_chip_erase - || self.config.restore_unwritten_bytes + if self.config.flashing_config.flashing_enabled + || self.config.flashing_config.reset_after_flashing + || self.config.flashing_config.halt_after_reset + || self.config.flashing_config.full_chip_erase + || self.config.flashing_config.restore_unwritten_bytes { debug_adapter.send_response::<()>( launch_attach_request, @@ -627,11 +627,11 @@ impl Debugger { return Err(error); } }; - debug_adapter.halt_after_reset = self.config.halt_after_reset; + debug_adapter.halt_after_reset = self.config.flashing_config.halt_after_reset; // Do the flashing. { - if self.config.flashing_enabled { + if self.config.flashing_config.flashing_enabled { let path_to_elf = match self.config.program_binary.clone() { Some(program_binary) => program_binary, None => { @@ -650,8 +650,9 @@ impl Debugger { let progress_id = debug_adapter.start_progress("Flashing device", None).ok(); let mut download_options = DownloadOptions::default(); - download_options.keep_unwritten_bytes = self.config.restore_unwritten_bytes; - download_options.do_chip_erase = self.config.full_chip_erase; + download_options.keep_unwritten_bytes = + self.config.flashing_config.restore_unwritten_bytes; + download_options.do_chip_erase = self.config.flashing_config.full_chip_erase; let flash_result = { let rc_debug_adapter = Rc::new(RefCell::new(debug_adapter)); let rc_debug_adapter_clone = rc_debug_adapter.clone(); @@ -881,7 +882,9 @@ impl Debugger { } }; - if self.config.flashing_enabled && self.config.reset_after_flashing { + if self.config.flashing_config.flashing_enabled + && self.config.flashing_config.reset_after_flashing + { debug_adapter .restart(&mut core_data, None) .context("Failed to restart core")?; diff --git a/debugger/src/debugger/session_data.rs b/debugger/src/debugger/session_data.rs index cb4dfe8826..071d7b1c5e 100644 --- a/debugger/src/debugger/session_data.rs +++ b/debugger/src/debugger/session_data.rs @@ -38,18 +38,21 @@ pub struct SessionData { pub(crate) session: Session, /// Provides ability to disassemble binary code. pub(crate) capstone: Capstone, - /// [SessionData] will manage one [DebugInfo] per [SessionConfig::program_binary] + /// [SessionData] will manage one [DebugInfo] per [CoreConfig::program_binary] pub(crate) debug_infos: Vec, + /// [SessionData] will manage one [PeripheralSpec] per [CoreConfig::svd_file] + pub(crate) peripherals: Vec, /// [SessionData] will manage a `Vec` per [Core]. Each core's collection of StackFrames will be recreated whenever a stacktrace is performed, using the results of [DebugInfo::unwind] pub(crate) stack_frames: Vec>, /// [SessionData] will manage a `Vec` per [Core]. Each core's collection of ActiveBreakpoint's will be managed on demand. pub(crate) breakpoints: Vec>, - /// The control structures for handling RTT in this Core of the SessionData. + /// The control structures for handling RTT. One per [CoreConfig::rtt_config]. pub(crate) rtt_connection: Option, } impl SessionData { pub(crate) fn new(config: &configuration::SessionConfig) -> Result { + // `SessionConfig` Probe/Session level configurations initialization. let mut target_probe = match config.probe_selector.clone() { Some(selector) => Probe::open(selector.clone()).map_err(|e| match e { DebugProbeError::ProbeCouldNotBeCreated(ProbeCreationError::NotFound) => { @@ -150,16 +153,32 @@ impl SessionData { })?; }; - // TODO: We currently only allow a single core & binary to be specified in [SessionConfig]. When this is extended to support multicore, the following should initialize [DebugInfo] and [VariableCache] for each available core. - // Configure the [DebugInfo]. - let debug_infos = vec![if let Some(binary_path) = &config.program_binary { - DebugInfo::from_file(binary_path) - .map_err(|error| DebuggerError::Other(anyhow!(error)))? - } else { - return Err( - anyhow!("Please provide a valid `program_binary` for this debug session").into(), - ); - }]; + // `FlashingConfig` probe level initialization. + + // `CoreConfig` probe level initialization. + if config.core_configs.len() != 1 { + // TODO: For multi-core, allow > 1. + return Err(DebuggerError::Other(anyhow!("probe-rs-debugger requires that one, and only one, core be configured for debugging."))); + } + let mut debug_infos = vec![]; + let mut peripherals = vec![]; + for core_configuration in &config.core_configs { + // Configure the [DebugInfo]. + let mut debug_infos = vec![ + if let Some(binary_path) = &core_configuration.program_binary { + debug_infos.push( + DebugInfo::from_file(binary_path) + .map_err(|error| DebuggerError::Other(anyhow!(error)))?, + ); + } else { + return Err(anyhow!( + "Please provide a valid `program_binary` for debug core: {:?}", + core_configuration.core_index + ) + .into()); + }, + ]; + } // Configure the [VariableCache]. let stack_frames = target_session.list_cores() @@ -189,6 +208,7 @@ impl SessionData { session: target_session, capstone, debug_infos, + peripherals, stack_frames, breakpoints, rtt_connection: None, From fa6ccf1a75d44c725884bb3d778ca4c726f3ac20 Mon Sep 17 00:00:00 2001 From: JackN Date: Mon, 21 Mar 2022 10:56:23 -0400 Subject: [PATCH 04/29] Refactor for multi-core & break into smaller files --- debugger/src/debug_adapter/dap_adapter.rs | 197 +++++++++------- debugger/src/debugger/configuration.rs | 108 ++++++--- debugger/src/debugger/core_data.rs | 60 +++-- debugger/src/debugger/debug_entry.rs | 271 ++++++++-------------- debugger/src/debugger/debug_rtt.rs | 10 +- debugger/src/debugger/session_data.rs | 227 +++++++++++------- 6 files changed, 477 insertions(+), 396 deletions(-) diff --git a/debugger/src/debug_adapter/dap_adapter.rs b/debugger/src/debug_adapter/dap_adapter.rs index 7565873ee5..2a81156cc8 100644 --- a/debugger/src/debug_adapter/dap_adapter.rs +++ b/debugger/src/debug_adapter/dap_adapter.rs @@ -1,6 +1,6 @@ use crate::{ debug_adapter::{dap_types, protocol::ProtocolAdapter}, - debugger::{configuration::ConsoleLog, core_data::CoreData, session_data::BreakpointType}, + debugger::{configuration::ConsoleLog, core_data::CoreHandle, session_data::BreakpointType}, DebuggerError, }; use anyhow::{anyhow, Result}; @@ -50,8 +50,8 @@ impl DebugAdapter

{ } } - pub(crate) fn status(&mut self, core_data: &mut CoreData, request: Request) -> Result<()> { - let status = match core_data.target_core.status() { + pub(crate) fn status(&mut self, target_core: &mut CoreHandle, request: Request) -> Result<()> { + let status = match target_core.core.status() { Ok(status) => { self.last_known_status = status; status @@ -67,9 +67,9 @@ impl DebugAdapter

{ } }; if status.is_halted() { - let pc = core_data - .target_core - .read_core_reg(core_data.target_core.registers().program_counter()); + let pc = target_core + .core + .read_core_reg(target_core.core.registers().program_counter()); match pc { Ok(pc) => self.send_response( request, @@ -88,12 +88,12 @@ impl DebugAdapter

{ } } - pub(crate) fn pause(&mut self, core_data: &mut CoreData, request: Request) -> Result<()> { + pub(crate) fn pause(&mut self, target_core: &mut CoreHandle, request: Request) -> Result<()> { // let args: PauseArguments = get_arguments(&request)?; - match core_data.target_core.halt(Duration::from_millis(500)) { + match target_core.core.halt(Duration::from_millis(500)) { Ok(cpu_info) => { - let new_status = match core_data.target_core.status() { + let new_status = match target_core.core.status() { Ok(new_status) => new_status, Err(error) => { self.send_response::<()>(request, Err(DebuggerError::ProbeRs(error)))?; @@ -104,7 +104,7 @@ impl DebugAdapter

{ let event_body = Some(StoppedEventBody { reason: "pause".to_owned(), description: Some(self.last_known_status.short_long_status().1.to_owned()), - thread_id: Some(core_data.target_core.id() as i64), + thread_id: Some(target_core.core.id() as i64), preserve_focus_hint: Some(false), text: None, all_threads_stopped: Some(false), // TODO: Implement multi-core logic here @@ -128,7 +128,11 @@ impl DebugAdapter

{ } } - pub(crate) fn read_memory(&mut self, core_data: &mut CoreData, request: Request) -> Result<()> { + pub(crate) fn read_memory( + &mut self, + target_core: &mut CoreHandle, + request: Request, + ) -> Result<()> { let arguments: ReadMemoryArguments = match get_arguments(&request) { Ok(arguments) => arguments, Err(error) => return self.send_response::<()>(request, Err(error)), @@ -159,7 +163,7 @@ impl DebugAdapter

{ let mut num_bytes_unread = arguments.count as usize; let mut buff = vec![]; while num_bytes_unread > 0 { - if let Ok(good_byte) = core_data.target_core.read_word_8(address) { + if let Ok(good_byte) = target_core.core.read_word_8(address) { buff.push(good_byte); address += 1; num_bytes_unread -= 1; @@ -190,7 +194,7 @@ impl DebugAdapter

{ pub(crate) fn write_memory( &mut self, - core_data: &mut CoreData, + target_core: &mut CoreHandle, request: Request, ) -> Result<()> { let arguments: WriteMemoryArguments = match get_arguments(&request) { @@ -231,8 +235,8 @@ impl DebugAdapter

{ ); } }; - match core_data - .target_core + match target_core + .core .write_8(address, &data_bytes) .map_err(DebuggerError::ProbeRs) { @@ -260,7 +264,11 @@ impl DebugAdapter

{ /// Evaluates the given expression in the context of the top most stack frame. /// The expression has access to any variables and arguments that are in scope. - pub(crate) fn evaluate(&mut self, core_data: &mut CoreData, request: Request) -> Result<()> { + pub(crate) fn evaluate( + &mut self, + target_core: &mut CoreHandle, + request: Request, + ) -> Result<()> { // TODO: When variables appear in the `watch` context, they will not resolve correctly after a 'step' function. Consider doing the lazy load for 'either/or' of Variables vs. Evaluate let arguments: EvaluateArguments = match get_arguments(&request) { @@ -284,13 +292,14 @@ impl DebugAdapter

{ // Make sure we have a valid StackFrame if let Some(stack_frame) = match arguments.frame_id { - Some(frame_id) => core_data + Some(frame_id) => target_core + .core_data .stack_frames .iter_mut() .find(|stack_frame| stack_frame.id == frame_id), None => { // Use the current frame_id - core_data.stack_frames.first_mut() + target_core.core_data.stack_frames.first_mut() } } { // Always search the registers first, because we don't have a VariableCache for them. @@ -355,7 +364,7 @@ impl DebugAdapter

{ /// Set the variable with the given name in the variable container to a new value. pub(crate) fn set_variable( &mut self, - core_data: &mut CoreData, + target_core: &mut CoreHandle, request: Request, ) -> Result<()> { let arguments: SetVariableArguments = match get_arguments(&request) { @@ -378,7 +387,8 @@ impl DebugAdapter

{ let parent_key = arguments.variables_reference; let new_value = arguments.value.clone(); - match core_data + match target_core + .core_data .stack_frames .iter_mut() .find(|stack_frame| stack_frame.id == parent_key) @@ -412,7 +422,7 @@ impl DebugAdapter

{ // The parent_key refers to a local or static variable in one of the in-scope StackFrames. let mut cache_variable: Option = None; let mut variable_cache: Option<&mut VariableCache> = None; - for search_frame in core_data.stack_frames.iter_mut() { + for search_frame in target_core.core_data.stack_frames.iter_mut() { if let Some(search_cache) = &mut search_frame.local_variables { if let Some(search_variable) = search_cache .get_variable_by_name_and_parent(&variable_name, Some(parent_key)) @@ -438,7 +448,7 @@ impl DebugAdapter

{ { // We have found the variable that needs to be updated. match cache_variable.update_value( - &mut core_data.target_core, + &mut target_core.core, variable_cache, new_value.clone(), ) { @@ -487,10 +497,10 @@ impl DebugAdapter

{ pub(crate) fn restart( &mut self, - core_data: &mut CoreData, + target_core: &mut CoreHandle, request: Option, ) -> Result<()> { - match core_data.target_core.halt(Duration::from_millis(500)) { + match target_core.core.halt(Duration::from_millis(500)) { Ok(_) => {} Err(error) => { if let Some(request) = request { @@ -506,12 +516,12 @@ impl DebugAdapter

{ // Different code paths if we invoke this from a request, versus an internal function. if let Some(request) = request { - match core_data.target_core.reset() { + match target_core.core.reset() { Ok(_) => { self.last_known_status = CoreStatus::Running; let event_body = Some(ContinuedEventBody { all_threads_continued: Some(false), // TODO: Implement multi-core logic here - thread_id: core_data.target_core.id() as i64, + thread_id: target_core.core.id() as i64, }); self.send_event("continued", event_body) @@ -526,10 +536,7 @@ impl DebugAdapter

{ } else { // The DAP Client will always do a `reset_and_halt`, and then will consider `halt_after_reset` value after the `configuration_done` request. // Otherwise the probe will run past the `main()` before the DAP Client has had a chance to set breakpoints in `main()`. - match core_data - .target_core - .reset_and_halt(Duration::from_millis(500)) - { + match target_core.core.reset_and_halt(Duration::from_millis(500)) { Ok(_) => { if let Some(request) = request { return self.send_response::<()>(request, Ok(None)); @@ -544,7 +551,7 @@ impl DebugAdapter

{ .1 .to_string(), ), - thread_id: Some(core_data.target_core.id() as i64), + thread_id: Some(target_core.core.id() as i64), preserve_focus_hint: None, text: None, all_threads_stopped: Some(false), // TODO: Implement multi-core logic here @@ -583,7 +590,7 @@ impl DebugAdapter

{ /// - Any other status will send and error. pub(crate) fn configuration_done( &mut self, - _core_data: &mut CoreData, + _core_data: &mut CoreHandle, request: Request, ) -> Result<()> { self.send_response::<()>(request, Ok(None)) @@ -591,7 +598,7 @@ impl DebugAdapter

{ pub(crate) fn set_breakpoints( &mut self, - core_data: &mut CoreData, + target_core: &mut CoreHandle, request: Request, ) -> Result<()> { let args: SetBreakpointsArguments = match get_arguments(&request) { @@ -612,7 +619,7 @@ impl DebugAdapter

{ let source_path = args.source.path.as_ref().map(Path::new); // Always clear existing breakpoints before setting new ones. The DAP Specification doesn't make allowances for deleting and setting individual breakpoints. - match core_data.clear_breakpoints(BreakpointType::SourceBreakpoint) { + match target_core.clear_breakpoints(BreakpointType::SourceBreakpoint) { Ok(_) => {} Err(error) => { return self.send_response::<()>( @@ -733,7 +740,7 @@ impl DebugAdapter

{ pub(crate) fn set_instruction_breakpoints( &mut self, - core_data: &mut CoreData, + target_core: &mut CoreHandle, request: Request, ) -> Result<()> { let arguments: SetInstructionBreakpointsArguments = match get_arguments(&request) { @@ -753,7 +760,7 @@ impl DebugAdapter

{ let mut created_breakpoints: Vec = Vec::new(); // Always clear existing breakpoints before setting new ones. - match core_data.clear_breakpoints(BreakpointType::InstructionBreakpoint) { + match target_core.clear_breakpoints(BreakpointType::InstructionBreakpoint) { Ok(_) => {} Err(error) => log::warn!("Failed to clear instruction breakpoints. {}", error), } @@ -782,7 +789,7 @@ impl DebugAdapter

{ requested_breakpoint.instruction_reference.parse() } { - match core_data + match target_core .set_breakpoint(memory_reference, BreakpointType::InstructionBreakpoint) { Ok(_) => { @@ -790,7 +797,8 @@ impl DebugAdapter

{ breakpoint_response.instruction_reference = Some(format!("{:#010x}", memory_reference)); // Try to resolve the source location for this breakpoint. - match core_data + match target_core + .core_data .debug_info .get_source_location(memory_reference as u64) { @@ -840,13 +848,13 @@ impl DebugAdapter

{ self.send_response(request, Ok(Some(instruction_breakpoint_body))) } - pub(crate) fn threads(&mut self, core_data: &mut CoreData, request: Request) -> Result<()> { + pub(crate) fn threads(&mut self, target_core: &mut CoreHandle, request: Request) -> Result<()> { // TODO: Implement actual thread resolution. For now, we just use the core id as the thread id. let mut threads: Vec = vec![]; match self.last_known_status { CoreStatus::Unknown => { // We are probably here because the `configuration_done` request just happened, so we can make sure the client and debugger are in synch. - match core_data.target_core.status() { + match target_core.core.status() { Ok(core_status) => { self.last_known_status = core_status; // Make sure the DAP Client and the DAP Server are in sync with the status of the core. @@ -859,7 +867,7 @@ impl DebugAdapter

{ description: Some( core_status.short_long_status().1.to_string(), ), - thread_id: Some(core_data.target_core.id() as i64), + thread_id: Some(target_core.core.id() as i64), preserve_focus_hint: None, text: None, all_threads_stopped: Some(false), // TODO: Implement multi-core logic here @@ -868,15 +876,15 @@ impl DebugAdapter

{ self.send_event("stopped", event_body)?; } else { let single_thread = Thread { - id: core_data.target_core.id() as i64, - name: core_data.target_name.clone(), + id: target_core.core.id() as i64, + name: target_core.core_data.target_name.clone(), }; threads.push(single_thread); self.send_response( request.clone(), Ok(Some(ThreadsResponseBody { threads })), )?; - return self.r#continue(core_data, request); + return self.r#continue(target_core, request); } } } @@ -893,14 +901,14 @@ impl DebugAdapter

{ } CoreStatus::Halted(_) => { let single_thread = Thread { - id: core_data.target_core.id() as i64, - name: core_data.target_name.clone(), + id: target_core.core.id() as i64, + name: target_core.core_data.target_name.clone(), }; threads.push(single_thread); // We do the actual stack trace here, because VSCode sometimes sends multiple StackTrace requests, which lead to unnecessary unwind processing. // By doing it here, we do it once, and serve up the results when we get the StackTrace requests. - let regs = core_data.target_core.registers(); - let pc = match core_data.target_core.read_core_reg(regs.program_counter()) { + let regs = target_core.core.registers(); + let pc = match target_core.core.read_core_reg(regs.program_counter()) { Ok(pc) => pc, Err(error) => { return self @@ -909,11 +917,13 @@ impl DebugAdapter

{ }; log::debug!( "Updating the stack frame data for core #{}", - core_data.target_core.id() + target_core.core.id() ); - *core_data.stack_frames = core_data + + target_core.core_data.stack_frames = target_core + .core_data .debug_info - .unwind(&mut core_data.target_core, u64::from(pc))?; + .unwind(&mut target_core.core, u64::from(pc))?; } CoreStatus::Running | CoreStatus::LockedUp | CoreStatus::Sleeping => { return self.send_response::<()>( @@ -928,8 +938,12 @@ impl DebugAdapter

{ self.send_response(request, Ok(Some(ThreadsResponseBody { threads }))) } - pub(crate) fn stack_trace(&mut self, core_data: &mut CoreData, request: Request) -> Result<()> { - let _status = match core_data.target_core.status() { + pub(crate) fn stack_trace( + &mut self, + target_core: &mut CoreHandle, + request: Request, + ) -> Result<()> { + let _status = match target_core.core.status() { Ok(status) => { if !status.is_halted() { return self.send_response::<()>( @@ -961,9 +975,9 @@ impl DebugAdapter

{ if let Some(levels) = arguments.levels { if let Some(start_frame) = arguments.start_frame { // Determine the correct 'slice' of available [StackFrame]s to serve up ... - let total_frames = core_data.stack_frames.len() as i64; + let total_frames = target_core.core_data.stack_frames.len() as i64; - // We need to copy some parts of StackFrame so that we can re-use it later without references to core_data. + // We need to copy some parts of StackFrame so that we can re-use it later without references to target_core. struct PartialStackFrameData { id: i64, function_name: String, @@ -974,13 +988,22 @@ impl DebugAdapter

{ let frame_set = if levels == 1 && start_frame == 0 { // Just the first frame - use the LHS of the split at `levels` - core_data.stack_frames.split_at(levels as usize).0 + target_core + .core_data + .stack_frames + .split_at(levels as usize) + .0 } else if total_frames <= 20 && start_frame >= 0 && start_frame <= total_frames { // When we have less than 20 frames - use the RHS of of the split at `start_frame` - core_data.stack_frames.split_at(start_frame as usize).1 + target_core + .core_data + .stack_frames + .split_at(start_frame as usize) + .1 } else if total_frames > 20 && start_frame + levels <= total_frames { - // When we have more than 20 frames - we can safely split twice. - core_data + // When we have more than 20 frames - we can safely split twice + target_core + .core_data .stack_frames .split_at(start_frame as usize) .1 @@ -1087,7 +1110,7 @@ impl DebugAdapter

{ /// - static scope : Variables with `static` modifier /// - registers : The [probe_rs::Core::registers] for the target [probe_rs::CoreType] /// - local scope : Variables defined between start of current frame, and the current pc (program counter) - pub(crate) fn scopes(&mut self, core_data: &mut CoreData, request: Request) -> Result<()> { + pub(crate) fn scopes(&mut self, target_core: &mut CoreHandle, request: Request) -> Result<()> { let arguments: ScopesArguments = match get_arguments(&request) { Ok(arguments) => arguments, Err(error) => return self.send_response::<()>(request, Err(error)), @@ -1097,7 +1120,7 @@ impl DebugAdapter

{ log::trace!("Getting scopes for frame {}", arguments.frame_id,); - if let Some(stack_frame) = core_data.get_stackframe(arguments.frame_id) { + if let Some(stack_frame) = target_core.get_stackframe(arguments.frame_id) { if let Some(static_root_variable) = stack_frame .static_variables @@ -1176,7 +1199,7 @@ impl DebugAdapter

{ /// Attempt to extract disassembled source code to supply the instruction_count required. pub(crate) fn get_disassembled_source( &mut self, - core_data: &mut CoreData, + target_core: &mut CoreHandle, // The program_counter where our desired instruction range is based. memory_reference: i64, // The number of bytes offset from the memory reference. Can be zero. @@ -1186,8 +1209,10 @@ impl DebugAdapter

{ // The EXACT number of instructions to return in the result. instruction_count: i64, ) -> Result, DebuggerError> { - let instruction_offset_as_bytes = - (instruction_offset * core_data.debug_info.get_instruction_size() as i64) / 4 * 5; + let instruction_offset_as_bytes = (instruction_offset + * target_core.core_data.debug_info.get_instruction_size() as i64) + / 4 + * 5; // The vector we will use to return results. let mut assembly_lines: Vec = vec![]; @@ -1212,7 +1237,7 @@ impl DebugAdapter

{ // The MS DAP spec requires that we always have to return a fixed number of instructions. while assembly_lines.len() < instruction_count as usize { if read_more_bytes { - match core_data.target_core.read_word_32(read_pointer) { + match target_core.core.read_word_32(read_pointer) { Ok(new_word) => { // Advance the read pointer for next time we need it. read_pointer += 4; @@ -1240,7 +1265,7 @@ impl DebugAdapter

{ } } - match core_data + match target_core .capstone .disasm_count(&code_buffer, instruction_pointer as u64, 1) { @@ -1267,7 +1292,8 @@ impl DebugAdapter

{ let mut location = None; let mut line = None; let mut column = None; - if let Some(current_source_location) = core_data + if let Some(current_source_location) = target_core + .core_data .debug_info .get_source_location(instruction.address()) { if let Some(previous_source_location) = stored_source_location.clone() { @@ -1340,7 +1366,11 @@ impl DebugAdapter

{ /// - Reached the required number of instructions. /// - We encounter 'unreadable' memory on the target. /// - In this case, pad the results with, as the api requires, "implementation defined invalid instructions" - pub(crate) fn disassemble(&mut self, core_data: &mut CoreData, request: Request) -> Result<()> { + pub(crate) fn disassemble( + &mut self, + target_core: &mut CoreHandle, + request: Request, + ) -> Result<()> { let arguments: DisassembleArguments = match get_arguments(&request) { Ok(arguments) => arguments, Err(error) => return self.send_response::<()>(request, Err(error)), @@ -1353,7 +1383,7 @@ impl DebugAdapter

{ arguments.memory_reference.parse() } { match self.get_disassembled_source( - core_data, + target_core, memory_reference as i64, arguments.offset.unwrap_or(0_i64), arguments.instruction_offset.unwrap_or(0_i64), @@ -1380,7 +1410,11 @@ impl DebugAdapter

{ } } - pub(crate) fn variables(&mut self, core_data: &mut CoreData, request: Request) -> Result<()> { + pub(crate) fn variables( + &mut self, + target_core: &mut CoreHandle, + request: Request, + ) -> Result<()> { let arguments: VariablesArguments = match get_arguments(&request) { Ok(arguments) => arguments, Err(error) => return self.send_response::<()>(request, Err(error)), @@ -1391,7 +1425,7 @@ impl DebugAdapter

{ let mut parent_variable: Option = None; let mut variable_cache: Option<&mut VariableCache> = None; let mut stack_frame_registers: Option<&Registers> = None; - for stack_frame in core_data.stack_frames.iter_mut() { + for stack_frame in target_core.core_data.stack_frames.iter_mut() { if let Some(search_cache) = &mut stack_frame.local_variables { if let Some(search_variable) = search_cache.get_variable_by_key(arguments.variables_reference) @@ -1462,9 +1496,9 @@ impl DebugAdapter

{ && !variable_cache.has_children(parent_variable)? { if let Some(stack_frame_registers) = stack_frame_registers { - core_data.debug_info.cache_deferred_variables( + target_core.core_data.debug_info.cache_deferred_variables( variable_cache, - &mut core_data.target_core, + &mut target_core.core, parent_variable, stack_frame_registers, )?; @@ -1532,13 +1566,14 @@ impl DebugAdapter

{ self.send_response(request, response) } - pub(crate) fn r#continue(&mut self, core_data: &mut CoreData, request: Request) -> Result<()> { - match core_data.target_core.run() { + pub(crate) fn r#continue( + &mut self, + target_core: &mut CoreHandle, + request: Request, + ) -> Result<()> { + match target_core.core.run() { Ok(_) => { - self.last_known_status = core_data - .target_core - .status() - .unwrap_or(CoreStatus::Unknown); + self.last_known_status = target_core.core.status().unwrap_or(CoreStatus::Unknown); if request.command.as_str() == "continue" { // If this continue was initiated as part of some other request, then do not respond. self.send_response( @@ -1552,13 +1587,13 @@ impl DebugAdapter

{ // but "immediately" afterwards, the MCU hits a breakpoint or exception. // So we have to check the status again to be sure. thread::sleep(Duration::from_millis(100)); // Small delay to make sure the MCU hits user breakpoints early in `main()`. - let core_status = match core_data.target_core.status() { + let core_status = match target_core.core.status() { Ok(new_status) => match new_status { CoreStatus::Halted(_) => { let event_body = Some(StoppedEventBody { reason: new_status.short_long_status().0.to_owned(), description: Some(new_status.short_long_status().1.to_string()), - thread_id: Some(core_data.target_core.id() as i64), + thread_id: Some(target_core.core.id() as i64), preserve_focus_hint: None, text: None, all_threads_stopped: Some(false), // TODO: Implement multi-core logic here diff --git a/debugger/src/debugger/configuration.rs b/debugger/src/debugger/configuration.rs index 84414aa07b..94cee71ba8 100644 --- a/debugger/src/debugger/configuration.rs +++ b/debugger/src/debugger/configuration.rs @@ -23,6 +23,9 @@ pub struct SessionConfig { #[serde(alias = "probe")] pub(crate) probe_selector: Option, + /// The target to be selected. + pub(crate) chip: Option, + /// Assert target's reset during connect #[serde(default)] pub(crate) connect_under_reset: bool, @@ -35,26 +38,76 @@ pub struct SessionConfig { pub(crate) protocol: Option, ///Allow the session to erase all memory of the chip or reset it to factory default. + #[serde(default)] pub(crate) allow_erase_all: bool, /// Flashing configuration - #[serde(flatten)] pub(crate) flashing_config: FlashingConfig, /// Every core on the target has certain configuration. /// /// NOTE: Although we allow specifying multiple core configurations, this is a work in progress, and probe-rs-debugger currently only supports debugging a single core. - #[serde(flatten)] pub(crate) core_configs: Vec, } impl SessionConfig { + /// Ensure all file names are correctly specified and that the files they point to are accessible. + pub(crate) fn validate_config_files(&mut self) -> Result<(), DebuggerError> { + // Update the `cwd`. + self.cwd = self.validate_and_update_cwd()?; + + for target_core_config in &mut self.core_configs { + // Update the `program_binary` and validate that the file exists. + target_core_config.program_binary = match qualify_and_update_os_file_path( + self.cwd.clone(), + target_core_config.program_binary.as_ref(), + ) { + Ok(program_binary) => { + if !program_binary.is_file() { + return Err(DebuggerError::Other(anyhow!( + "Invalid program binary file specified '{:?}'", + program_binary + ))); + } + Some(program_binary) + } + Err(error) => { + return Err(DebuggerError::Other(anyhow!( + "Please use the `program-binary` option to specify an executable for this target core. {:?}", error + ))); + } + }; + // Update the `svd_file` and validate that the file exists. + // If there is a problem with this file, warn the user and continue with the session. + target_core_config.svd_file = match qualify_and_update_os_file_path( + self.cwd.clone(), + target_core_config.svd_file.as_ref(), + ) { + Ok(svd_file) => { + if !svd_file.is_file() { + log::error!("SVD file {:?} not found.", svd_file); + None + } else { + Some(svd_file) + } + } + Err(error) => { + // SVD file is not mandatory. + log::debug!("SVD file not specified: {:?}", &error); + None + } + }; + } + + Ok(()) + } + /// Validate the new cwd, or else set it from the environment. - pub(crate) fn validate_and_update_cwd(&mut self, new_cwd: Option) { - self.cwd = match new_cwd { + pub(crate) fn validate_and_update_cwd(&self) -> Result, DebuggerError> { + Ok(match &self.cwd { Some(temp_path) => { if temp_path.is_dir() { - Some(temp_path) + Some(temp_path.to_path_buf()) } else if let Ok(current_dir) = current_dir() { Some(current_dir) } else { @@ -70,32 +123,32 @@ impl SessionConfig { None } } - }; + }) } +} - /// If the path to the program to be debugged is relative, we join if with the cwd. - pub(crate) fn qualify_and_update_os_file_path( - &mut self, - os_file_to_validate: Option, - ) -> Result { - match os_file_to_validate { - Some(temp_path) => { - let mut new_path = PathBuf::new(); - if temp_path.is_relative() { - if let Some(cwd_path) = self.cwd.clone() { - new_path.push(cwd_path); - } else { - return Err(DebuggerError::Other(anyhow!( - "Invalid value {:?} for `cwd`", - self.cwd - ))); - } +/// If the path to the program to be debugged is relative, we join if with the cwd. +fn qualify_and_update_os_file_path( + configured_cwd: Option, + os_file_to_validate: Option<&PathBuf>, +) -> Result { + match os_file_to_validate { + Some(temp_path) => { + let mut new_path = PathBuf::new(); + if temp_path.is_relative() { + if let Some(cwd_path) = configured_cwd.clone() { + new_path.push(cwd_path); + } else { + return Err(DebuggerError::Other(anyhow!( + "Invalid value {:?} for `cwd`", + configured_cwd + ))); } - new_path.push(temp_path); - Ok(new_path) } - None => Err(DebuggerError::Other(anyhow!("Missing value for file."))), + new_path.push(temp_path); + Ok(new_path) } + None => Err(DebuggerError::Other(anyhow!("Missing value for file."))), } } @@ -132,9 +185,6 @@ pub struct CoreConfig { #[serde(default)] pub(crate) core_index: usize, - /// The target to be selected. - pub(crate) chip: Option, - /// Binary to debug as a path. Relative to `cwd`, or fully qualified. pub(crate) program_binary: Option, diff --git a/debugger/src/debugger/core_data.rs b/debugger/src/debugger/core_data.rs index adc0504c28..9c5a330bdb 100644 --- a/debugger/src/debugger/core_data.rs +++ b/debugger/src/debugger/core_data.rs @@ -9,24 +9,31 @@ use capstone::Capstone; use probe_rs::{debug::DebugInfo, Core}; use probe_rs_cli_util::rtt; -/// [CoreData] provides handles to various data structures required to debug a single instance of a core. The actual state is stored in [SessionData]. +/// [CoreData] is used to cache data needed by the debugger, on a per-core basis. +pub struct CoreData { + pub(crate) core_index: usize, + pub(crate) target_name: String, + pub(crate) debug_info: DebugInfo, + pub(crate) core_peripherals: Option, + pub(crate) stack_frames: Vec, + pub(crate) breakpoints: Vec, + pub(crate) rtt_connection: Option, +} + +/// [CoreHandle] provides handles to various data structures required to debug a single instance of a core. The actual state is stored in [SessionData]. /// /// Usage: To get access to this structure please use the [SessionData::attach_core] method. Please keep access/locks to this to a minumum duration. -pub struct CoreData<'p> { - pub(crate) target_core: Core<'p>, - pub(crate) target_name: String, - pub(crate) debug_info: &'p DebugInfo, - pub(crate) peripherals: &'p DebugInfo, - pub(crate) stack_frames: &'p mut Vec, +pub struct CoreHandle<'p> { + pub(crate) core: Core<'p>, pub(crate) capstone: &'p Capstone, - pub(crate) breakpoints: &'p mut Vec, - pub(crate) rtt_connection: &'p mut Option, + pub(crate) core_data: &'p mut CoreData, } -impl<'p> CoreData<'p> { +impl<'p> CoreHandle<'p> { /// Search available [StackFrame]'s for the given `id` pub(crate) fn get_stackframe(&'p self, id: i64) -> Option<&'p probe_rs::debug::StackFrame> { - self.stack_frames + self.core_data + .stack_frames .iter() .find(|stack_frame| stack_frame.id == id) } @@ -41,7 +48,7 @@ impl<'p> CoreData<'p> { ) -> Result<()> { let mut debugger_rtt_channels: Vec = vec![]; match rtt::attach_to_rtt( - &mut self.target_core, + &mut self.core, target_memory_map, program_binary, rtt_config, @@ -61,7 +68,7 @@ impl<'p> CoreData<'p> { ); } } - *self.rtt_connection = Some(debug_rtt::RttConnection { + self.core_data.rtt_connection = Some(debug_rtt::RttConnection { target_rtt, debugger_rtt_channels, }); @@ -73,46 +80,51 @@ impl<'p> CoreData<'p> { Ok(()) } - /// Set a single breakpoint in target configuration as well as [`CoreData::breakpoints`] + /// Set a single breakpoint in target configuration as well as [`CoreHandle::breakpoints`] pub(crate) fn set_breakpoint( &mut self, address: u32, breakpoint_type: session_data::BreakpointType, ) -> Result<(), DebuggerError> { - self.target_core + self.core .set_hw_breakpoint(address) .map_err(DebuggerError::ProbeRs)?; - self.breakpoints.push(session_data::ActiveBreakpoint { - breakpoint_type, - breakpoint_address: address, - }); + self.core_data + .breakpoints + .push(session_data::ActiveBreakpoint { + breakpoint_type, + breakpoint_address: address, + }); Ok(()) } - /// Clear a single breakpoint from target configuration as well as [`CoreData::breakpoints`] + /// Clear a single breakpoint from target configuration as well as [`CoreHandle::breakpoints`] pub(crate) fn clear_breakpoint(&mut self, address: u32) -> Result<()> { - self.target_core + self.core .clear_hw_breakpoint(address) .map_err(DebuggerError::ProbeRs)?; let mut breakpoint_position: Option = None; - for (position, active_breakpoint) in self.breakpoints.iter().enumerate() { + for (position, active_breakpoint) in self.core_data.breakpoints.iter().enumerate() { if active_breakpoint.breakpoint_address == address { breakpoint_position = Some(position); break; } } if let Some(breakpoint_position) = breakpoint_position { - self.breakpoints.remove(breakpoint_position as usize); + self.core_data + .breakpoints + .remove(breakpoint_position as usize); } Ok(()) } - /// Clear all breakpoints of a specified [`BreakpointType`]. Affects target configuration as well as [`CoreData::breakpoints`] + /// Clear all breakpoints of a specified [`BreakpointType`]. Affects target configuration as well as [`CoreHandle::breakpoints`] pub(crate) fn clear_breakpoints( &mut self, breakpoint_type: session_data::BreakpointType, ) -> Result<()> { let target_breakpoints = self + .core_data .breakpoints .iter() .filter(|breakpoint| breakpoint.breakpoint_type == breakpoint_type) diff --git a/debugger/src/debugger/debug_entry.rs b/debugger/src/debugger/debug_entry.rs index 1e7fa56d91..c3705b662c 100644 --- a/debugger/src/debugger/debug_entry.rs +++ b/debugger/src/debugger/debug_entry.rs @@ -70,6 +70,13 @@ impl Debugger { } } + /// The logic of this function is as follows: + /// - While we are waiting for DAP-Client (TCP or STDIO), we have to continuously check in on the status of the probe. + /// - Initally, while `LAST_KNOWN_STATUS` probe-rs::CoreStatus::Unknown, we do nothing. Wait until latter part of `debug_session` sets it to something known. + /// - If the `LAST_KNOWN_STATUS` is `Halted`, then we stop polling the Probe until the next DAP-Client request attempts an action + /// - If the `new_status` is an Err, then the probe is no longer available, and we end the debugging session + /// - If the `new_status` is different from the `LAST_KNOWN_STATUS`, then we have to tell the DAP-Client by way of an `Event` + /// - If the `new_status` is `Running`, then we have to poll on a regular basis, until the Probe stops for good reasons like breakpoints, or bad reasons like panics. Then tell the DAP-Client. pub(crate) fn process_next_request( &mut self, session_data: &mut session_data::SessionData, @@ -78,59 +85,42 @@ impl Debugger { let request = debug_adapter.listen_for_request()?; match request { None => { - /* - The logic of this command is as follows: - - While we are waiting for DAP-Client (TCP or STDIO), we have to continuously check in on the status of the probe. - - Initally, while `LAST_KNOWN_STATUS` probe-rs::CoreStatus::Unknown, we do nothing. Wait until latter part of `debug_session` sets it to something known. - - If the `LAST_KNOWN_STATUS` is `Halted`, then we stop polling the Probe until the next DAP-Client request attempts an action - - If the `new_status` is an Err, then the probe is no longer available, and we end the debugging session - - If the `new_status` is different from the `LAST_KNOWN_STATUS`, then we have to tell the DAP-Client by way of an `Event` - - If the `new_status` is `Running`, then we have to poll on a regular basis, until the Probe stops for good reasons like breakpoints, or bad reasons like panics. Then tell the DAP-Client. - */ + // If there are no requests, we poll target cores for status, which includes handling RTT data. match debug_adapter.last_known_status { - CoreStatus::Unknown => Ok(DebuggerStatus::ContinueSession), // Don't do anything until we know VSCode's startup sequence is complete, and changes this to either Halted or Running. + CoreStatus::Unknown => { + // Don't do anything until we know VSCode's startup sequence is complete, and changes this to either Halted or Running. + Ok(DebuggerStatus::ContinueSession) + } CoreStatus::Halted(_) => { - // Make sure the RTT buffers are drained. - match session_data.attach_core(self.config.core_index) { - Ok(mut core_data) => { - if let Some(rtt_active_target) = &mut core_data.rtt_connection { - rtt_active_target.process_rtt_data( - debug_adapter, - &mut core_data.target_core, - ); - }; - } - Err(error) => { - let _ = debug_adapter.send_error_response(&error)?; - return Err(error); - } - }; - + session_data.poll_rtt(&self.config, debug_adapter); // No need to poll the target status if we know it is halted and waiting for us to do something. thread::sleep(Duration::from_millis(50)); // Small delay to reduce fast looping costs on the client Ok(DebuggerStatus::ContinueSession) } _other => { - let mut received_rtt_data = false; - let mut core_data = match session_data.attach_core(self.config.core_index) { - Ok(mut core_data) => { - // Use every opportunity to poll the RTT channels for data - if let Some(rtt_active_target) = &mut core_data.rtt_connection { - received_rtt_data = rtt_active_target.process_rtt_data( - debug_adapter, - &mut core_data.target_core, - ); - } - core_data - } - Err(error) => { - let _ = debug_adapter.send_error_response(&error)?; - return Err(error); + let received_rtt_data = session_data.poll_rtt(&self.config, debug_adapter); + + // Check and update the core status. + // TODO: This only works for a single core, so until it can be redesigned, will use the first one configured. + let mut target_core = if let Some(target_core_config) = + self.config.core_configs.first_mut() + { + if let Ok(core_handle) = + session_data.attach_core(target_core_config.core_index) + { + core_handle + } else { + return Err(DebuggerError::Other(anyhow!( + "Unable to connect to target core" + ))); } + } else { + return Err(DebuggerError::Other(anyhow!( + "Cannot continue unless one target core configuration is defined." + ))); }; - // Check and update the core status. - let new_status = match core_data.target_core.status() { + let new_status = match target_core.core.status() { Ok(new_status) => new_status, Err(error) => { let error = DebuggerError::ProbeRs(error); @@ -155,7 +145,7 @@ impl Debugger { CoreStatus::Running | CoreStatus::Sleeping => { let event_body = Some(ContinuedEventBody { all_threads_continued: Some(true), - thread_id: core_data.target_core.id() as i64, + thread_id: target_core.core.id() as i64, }); debug_adapter.send_event("continued", event_body)?; } @@ -163,7 +153,7 @@ impl Debugger { let event_body = Some(StoppedEventBody { reason: new_status.short_long_status().0.to_owned(), description: Some(new_status.short_long_status().1.to_owned()), - thread_id: Some(core_data.target_core.id() as i64), + thread_id: Some(target_core.core.id() as i64), preserve_focus_hint: Some(false), text: None, all_threads_stopped: Some(true), @@ -197,16 +187,22 @@ impl Debugger { } Some(request) => { // First, attach to the core. - let mut core_data = match session_data.attach_core(self.config.core_index) { - Ok(core_data) => core_data, - Err(error) => { - let failed_command = request.command.clone(); - debug_adapter.send_response::<()>(request, Err(error))?; + // TODO: This only works for a single core, so until it can be redesigned, will use the first one configured. + let mut target_core = if let Some(target_core_config) = + self.config.core_configs.first_mut() + { + if let Ok(core_handle) = session_data.attach_core(target_core_config.core_index) + { + core_handle + } else { return Err(DebuggerError::Other(anyhow!( - "Error while attaching to core. Could not complete command {}", - failed_command + "Unable to connect to target core" ))); } + } else { + return Err(DebuggerError::Other(anyhow!( + "Cannot continue unless one target core configuration is defined." + ))); }; // For some operations, we need to make sure the core isn't sleeping, by calling `Core::halt()`. @@ -226,10 +222,10 @@ impl Debugger { | "readMemory" | "writeMemory" | "disassemble" => { - match core_data.target_core.status() { + match target_core.core.status() { Ok(current_status) => { if current_status == CoreStatus::Sleeping { - match core_data.target_core.halt(Duration::from_millis(100)) { + match target_core.core.halt(Duration::from_millis(100)) { Ok(_) => { debug_adapter.last_known_status = CoreStatus::Halted(probe_rs::HaltReason::Request); @@ -264,7 +260,9 @@ impl Debugger { // Now we are ready to execute supported commands, or return an error if it isn't supported. match match request.command.clone().as_ref() { "rttWindowOpened" => { - if let Some(debugger_rtt_target) = core_data.rtt_connection { + if let Some(debugger_rtt_target) = + target_core.core_data.rtt_connection.as_mut() + { match get_arguments::(&request) { Ok(arguments) => { debugger_rtt_target @@ -298,13 +296,13 @@ impl Debugger { .send_response::<()>(request, Ok(None)) .and(Ok(DebuggerStatus::TerminateSession)), "terminate" => debug_adapter - .pause(&mut core_data, request) + .pause(&mut target_core, request) .and(Ok(DebuggerStatus::TerminateSession)), "status" => debug_adapter - .status(&mut core_data, request) + .status(&mut target_core, request) .and(Ok(DebuggerStatus::ContinueSession)), "next" => debug_adapter - .next(&mut core_data, request) + .next(&mut target_core, request) .and(Ok(DebuggerStatus::ContinueSession)), "stepIn" => debug_adapter .step_in(&mut core_data, request) @@ -313,53 +311,53 @@ impl Debugger { .step_out(&mut core_data, request) .and(Ok(DebuggerStatus::ContinueSession)), "pause" => debug_adapter - .pause(&mut core_data, request) + .pause(&mut target_core, request) .and(Ok(DebuggerStatus::ContinueSession)), "readMemory" => debug_adapter - .read_memory(&mut core_data, request) + .read_memory(&mut target_core, request) .and(Ok(DebuggerStatus::ContinueSession)), "writeMemory" => debug_adapter - .write_memory(&mut core_data, request) + .write_memory(&mut target_core, request) .and(Ok(DebuggerStatus::ContinueSession)), "setVariable" => debug_adapter - .set_variable(&mut core_data, request) + .set_variable(&mut target_core, request) .and(Ok(DebuggerStatus::ContinueSession)), "configurationDone" => debug_adapter - .configuration_done(&mut core_data, request) + .configuration_done(&mut target_core, request) .and(Ok(DebuggerStatus::ContinueSession)), "threads" => debug_adapter - .threads(&mut core_data, request) + .threads(&mut target_core, request) .and(Ok(DebuggerStatus::ContinueSession)), "restart" => { // Reset RTT so that the link can be re-established - *core_data.rtt_connection = None; + target_core.core_data.rtt_connection = None; debug_adapter - .restart(&mut core_data, Some(request)) + .restart(&mut target_core, Some(request)) .and(Ok(DebuggerStatus::ContinueSession)) } "setBreakpoints" => debug_adapter - .set_breakpoints(&mut core_data, request) + .set_breakpoints(&mut target_core, request) .and(Ok(DebuggerStatus::ContinueSession)), "setInstructionBreakpoints" => debug_adapter - .set_instruction_breakpoints(&mut core_data, request) + .set_instruction_breakpoints(&mut target_core, request) .and(Ok(DebuggerStatus::ContinueSession)), "stackTrace" => debug_adapter - .stack_trace(&mut core_data, request) + .stack_trace(&mut target_core, request) .and(Ok(DebuggerStatus::ContinueSession)), "scopes" => debug_adapter - .scopes(&mut core_data, request) + .scopes(&mut target_core, request) .and(Ok(DebuggerStatus::ContinueSession)), "disassemble" => debug_adapter - .disassemble(&mut core_data, request) + .disassemble(&mut target_core, request) .and(Ok(DebuggerStatus::ContinueSession)), "variables" => debug_adapter - .variables(&mut core_data, request) + .variables(&mut target_core, request) .and(Ok(DebuggerStatus::ContinueSession)), "continue" => debug_adapter - .r#continue(&mut core_data, request) + .r#continue(&mut target_core, request) .and(Ok(DebuggerStatus::ContinueSession)), "evaluate" => debug_adapter - .evaluate(&mut core_data, request) + .evaluate(&mut target_core, request) .and(Ok(DebuggerStatus::ContinueSession)), other_command => { // Unimplemented command. @@ -371,7 +369,7 @@ impl Debugger { } { Ok(debugger_status) => { if unhalt_me { - match core_data.target_core.run() { + match target_core.core.run() { Ok(_) => debug_adapter.last_known_status = CoreStatus::Running, Err(error) => { debug_adapter.send_error_response(&DebuggerError::Other( @@ -517,6 +515,7 @@ impl Debugger { }; }; + // TODO: Multi-core: This currently only supports the first `SessionConfig::core_configs` match get_arguments(&launch_attach_request) { Ok(arguments) => { if requested_target_session_type.is_some() { @@ -546,64 +545,7 @@ impl Debugger { debug_adapter.set_console_log_level( self.config.console_log_level.unwrap_or(ConsoleLog::Error), ); - // Update the `cwd` and `program_binary`. - self.config.validate_and_update_cwd(self.config.cwd.clone()); - // Update the `program_binary` and validate that the file exists. - self.config.program_binary = match self - .config - .qualify_and_update_os_file_path(self.config.program_binary.clone()) - { - Ok(program_binary) => { - if !program_binary.is_file() { - debug_adapter.send_response::<()>( - launch_attach_request, - Err(DebuggerError::Other(anyhow!( - "program_binary file {:?} not found.", - program_binary - ))), - )?; - return Err(DebuggerError::Other(anyhow!( - "Invalid program binary file specified '{:?}'", - program_binary - ))); - } - Some(program_binary) - } - Err(error) => { - debug_adapter.send_response::<()>( - launch_attach_request, - Err(DebuggerError::Other(anyhow!( - "Please use the --program-binary option to specify an executable: {:?}", error - ))), - )?; - return Err(DebuggerError::Other(anyhow!( - "Please use the --program-binary option to specify an executable" - ))); - } - }; - // Update the `svd_file` and validate that the file exists. - // If there is a problem with this file, warn the user and continue with the session. - self.config.svd_file = match self - .config - .qualify_and_update_os_file_path(self.config.svd_file.clone()) - { - Ok(svd_file) => { - if !svd_file.is_file() { - debug_adapter.show_message( - MessageSeverity::Warning, - format!("SVD file {:?} not found.", svd_file), - ); - None - } else { - Some(svd_file) - } - } - Err(error) => { - // SVD file is not mandatory. - log::debug!("SVD file not specified: {:?}", &error); - None - } - }; + debug_adapter.send_response::<()>(launch_attach_request, Ok(None))?; } Err(error) => { @@ -627,12 +569,32 @@ impl Debugger { return Err(error); } }; - debug_adapter.halt_after_reset = self.config.flashing_config.halt_after_reset; + // Validate file specifications in the config. + match self.config.validate_config_files() { + Ok(_) => {} + Err(error) => { + return Err(error); + } + }; + + // TODO: Currently the logic of processing MS DAP requests and executing them, is based on having a single core. It needs to be re-thought for multiple cores. Not all DAP requests require access to the core. One possible is to do the core attach inside each of the request implementations for those that need it, because the applicable core_index can be read from the request arguments. + // TODO: Until we refactor this, we only support a single core (always the first one specified in `SessionConfig::core_configs`) + let target_core_config = + if let Some(target_core_config) = self.config.core_configs.first_mut() { + target_core_config + } else { + return Err(DebuggerError::Other(anyhow!( + "Cannot continue unless one target core configuration is defined." + ))); + }; + + debug_adapter.halt_after_reset = self.config.flashing_config.halt_after_reset; // Do the flashing. + // TODO: Multi-core ... needs to flash multiple binaries { if self.config.flashing_config.flashing_enabled { - let path_to_elf = match self.config.program_binary.clone() { + let path_to_elf = match &target_core_config.program_binary { Some(program_binary) => program_binary, None => { let err = DebuggerError::Other(anyhow!( @@ -863,18 +825,18 @@ impl Debugger { // This is the first attach to the requested core. If this one works, all subsequent ones will be no-op requests for a Core reference. Do NOT hold onto this reference for the duration of the session ... that is why this code is in a block of its own. { // First, attach to the core - let mut core_data = match session_data.attach_core(self.config.core_index) { - Ok(mut core_data) => { + let mut target_core = match session_data.attach_core(target_core_config.core_index) { + Ok(mut target_core) => { // Immediately after attaching, halt the core, so that we can finish initalization without bumping into user code. // Depending on supplied `config`, the core will be restarted at the end of initialization in the `configuration_done` request. - match halt_core(&mut core_data.target_core) { + match halt_core(&mut target_core.core) { Ok(_) => {} Err(error) => { debug_adapter.send_error_response(&error)?; return Err(error); } } - core_data + target_core } Err(error) => { debug_adapter.send_error_response(&error)?; @@ -886,7 +848,7 @@ impl Debugger { && self.config.flashing_config.reset_after_flashing { debug_adapter - .restart(&mut core_data, None) + .restart(&mut target_core, None) .context("Failed to restart core")?; } } @@ -909,34 +871,7 @@ impl Debugger { loop { match self.process_next_request(&mut session_data, &mut debug_adapter) { Ok(DebuggerStatus::ContinueSession) => { - // Validate and if necessary, initialize the RTT structure. - if self.config.rtt.enabled - && session_data.rtt_connection.is_none() - && !(debug_adapter.last_known_status == CoreStatus::Unknown - || debug_adapter.last_known_status.is_halted()) - // Do not attempt this until we have processed the MSDAP request for "configurationDone" ... - { - let target_memory_map = session_data.session.target().memory_map.clone(); - let mut core_data = match session_data.attach_core(self.config.core_index) { - Ok(core_data) => core_data, - Err(error) => { - debug_adapter.send_error_response(&error)?; - return Err(error); - } - }; - log::info!("Attempting to initialize the RTT."); - // RTT can only be initialized if the target application has been allowed to run to the point where it does the RTT initialization. - // If the target halts before it processes this code, then this RTT intialization silently fails, and will try again later ... - // See `probe-rs-rtt::Rtt` for more information. - // We can safely unwrap() program_binary here, because it is validated to exist at startup of the debugger - #[allow(clippy::unwrap_used)] - core_data.attach_to_rtt( - &mut debug_adapter, - &target_memory_map, - self.config.program_binary.as_ref().unwrap(), - &self.config.rtt, - )?; - } + // All is good. We can process the next request. } Ok(DebuggerStatus::TerminateSession) => { return Ok(DebuggerStatus::TerminateSession); diff --git a/debugger/src/debugger/debug_rtt.rs b/debugger/src/debugger/debug_rtt.rs index a2b95bb0ee..82f3084220 100644 --- a/debugger/src/debugger/debug_rtt.rs +++ b/debugger/src/debugger/debug_rtt.rs @@ -20,10 +20,12 @@ impl RttConnection { ) -> bool { let mut at_least_one_channel_had_data = false; for debugger_rtt_channel in self.debugger_rtt_channels.iter_mut() { - if debugger_rtt_channel.poll_rtt_data(target_core, debug_adapter, &mut self.target_rtt) - { - at_least_one_channel_had_data = true; - } + at_least_one_channel_had_data = at_least_one_channel_had_data + | debugger_rtt_channel.poll_rtt_data( + target_core, + debug_adapter, + &mut self.target_rtt, + ) } at_least_one_channel_had_data } diff --git a/debugger/src/debugger/session_data.rs b/debugger/src/debugger/session_data.rs index 071d7b1c5e..acc2b1731c 100644 --- a/debugger/src/debugger/session_data.rs +++ b/debugger/src/debugger/session_data.rs @@ -1,6 +1,7 @@ -use super::configuration; -use super::core_data::CoreData; -use crate::debugger::debug_rtt; +use super::configuration::{self, CoreConfig, SessionConfig}; +use super::core_data::{CoreData, CoreHandle}; +use crate::debug_adapter::dap_adapter::DebugAdapter; +use crate::debug_adapter::protocol::ProtocolAdapter; use crate::DebuggerError; use anyhow::{anyhow, Result}; use capstone::Endian; @@ -10,11 +11,11 @@ use capstone::{ }; use probe_rs::config::TargetSelector; use probe_rs::debug::DebugInfo; -use probe_rs::DebugProbeError; use probe_rs::Permissions; use probe_rs::Probe; use probe_rs::ProbeCreationError; use probe_rs::Session; +use probe_rs::{CoreStatus, DebugProbeError}; use std::env::set_current_dir; /// The supported breakpoint types @@ -31,23 +32,15 @@ pub struct ActiveBreakpoint { pub(crate) breakpoint_address: u32, } -/// SessionData is designed to be similar to [probe_rs::Session], in as much that it provides handles to the [CoreData] instances for each of the available [probe_rs::Core] involved in the debug session. -/// To get access to the [CoreData] for a specific [Core], the +/// SessionData is designed to be similar to [probe_rs::Session], in as much that it provides handles to the [CoreHandle] instances for each of the available [probe_rs::Core] involved in the debug session. +/// To get access to the [CoreHandle] for a specific [Core], the /// TODO: Adjust [SessionConfig] to allow multiple cores (and if appropriate, their binaries) to be specified. pub struct SessionData { pub(crate) session: Session, /// Provides ability to disassemble binary code. pub(crate) capstone: Capstone, - /// [SessionData] will manage one [DebugInfo] per [CoreConfig::program_binary] - pub(crate) debug_infos: Vec, - /// [SessionData] will manage one [PeripheralSpec] per [CoreConfig::svd_file] - pub(crate) peripherals: Vec, - /// [SessionData] will manage a `Vec` per [Core]. Each core's collection of StackFrames will be recreated whenever a stacktrace is performed, using the results of [DebugInfo::unwind] - pub(crate) stack_frames: Vec>, - /// [SessionData] will manage a `Vec` per [Core]. Each core's collection of ActiveBreakpoint's will be managed on demand. - pub(crate) breakpoints: Vec>, - /// The control structures for handling RTT. One per [CoreConfig::rtt_config]. - pub(crate) rtt_connection: Option, + /// [SessionData] will manage one [CoreData] per target core, that is also present in [SessionConfig::core_configs] + pub(crate) core_data: Vec, } impl SessionData { @@ -127,6 +120,7 @@ impl SessionData { }; // Create an instance of the [`capstone::Capstone`] for disassembly capabilities. + // TODO: I believe it is safe to share this between multiple cores, but needs to be tested. let capstone = match target_session.architecture() { probe_rs::Architecture::Arm => Capstone::new() .arm() @@ -160,92 +154,145 @@ impl SessionData { // TODO: For multi-core, allow > 1. return Err(DebuggerError::Other(anyhow!("probe-rs-debugger requires that one, and only one, core be configured for debugging."))); } - let mut debug_infos = vec![]; - let mut peripherals = vec![]; - for core_configuration in &config.core_configs { - // Configure the [DebugInfo]. - let mut debug_infos = vec![ - if let Some(binary_path) = &core_configuration.program_binary { - debug_infos.push( - DebugInfo::from_file(binary_path) - .map_err(|error| DebuggerError::Other(anyhow!(error)))?, - ); - } else { - return Err(anyhow!( - "Please provide a valid `program_binary` for debug core: {:?}", - core_configuration.core_index - ) - .into()); - }, - ]; - } - // Configure the [VariableCache]. - let stack_frames = target_session.list_cores() + // Filter `CoreConfig` entries based on those that match an actual core on the target probe. + let valid_core_configs = config + .core_configs .iter() - .map(|(core_id, core_type)| { - log::debug!( - "Preparing stack frame variable cache for SessionData and CoreData for core #{} of type: {:?}", - core_id, - core_type - ); - Vec::::new() - }).collect(); + .filter(|&core_config| { + if let Some(_) = target_session + .list_cores() + .iter() + .find(|(target_core_index, _)| *target_core_index == core_config.core_index) + { + true + } else { + false + } + }) + .cloned() + .collect::>(); - // Prepare the breakpoint cache - let breakpoints = target_session.list_cores() - .iter() - .map(|(core_id, core_type)| { - log::debug!( - "Preparing breakpoint cache for SessionData and CoreData for core #{} of type: {:?}", - core_id, - core_type - ); - Vec::::new() - }).collect(); + let mut core_data_vec = vec![]; + + for core_configuration in &valid_core_configs { + // Configure the [DebugInfo]. + let debug_info = if let Some(binary_path) = &core_configuration.program_binary { + DebugInfo::from_file(binary_path) + .map_err(|error| DebuggerError::Other(anyhow!(error)))? + } else { + return Err(anyhow!( + "Please provide a valid `program_binary` for debug core: {:?}", + core_configuration.core_index + ) + .into()); + }; + + // Configure the [CorePeripherals]. + let core_peripherals = if let Some(svd_file) = &core_configuration.svd_file { + // TODO: To be implemented. + None + } else { + // Loading core_peripherals from a CMSIS-SVD file is optional. + None + // return Err(anyhow!( + // "Please provide a valid `program_binary` for debug core: {:?}", + // core_configuration.core_index + // ) + // .into()); + }; + + core_data_vec.push(CoreData { + core_index: core_configuration.core_index, + target_name: format!( + "{}-{}", + core_configuration.core_index, + target_session.target().name + ), + debug_info, + core_peripherals, + stack_frames: Vec::::new(), + breakpoints: Vec::::new(), + rtt_connection: None, + }) + } Ok(SessionData { session: target_session, capstone, - debug_infos, - peripherals, - stack_frames, - breakpoints, - rtt_connection: None, + core_data: core_data_vec, }) } - pub fn attach_core(&mut self, core_index: usize) -> Result { - let target_name = self.session.target().name.clone(); - // Do a 'light weight'(just get references to existing data structures) attach to the core and return relevant debug data. - match self.session.core(core_index) { - Ok(target_core) => Ok(CoreData { - target_core, - target_name: format!("{}-{}", core_index, target_name), - debug_info: self.debug_infos.get(core_index).ok_or_else(|| { - DebuggerError::Other(anyhow!( - "No available `DebugInfo` for core # {}", - core_index - )) - })?, - stack_frames: self.stack_frames.get_mut(core_index).ok_or_else(|| { - DebuggerError::Other(anyhow!( - "StackFrame cache was not correctly configured for core # {}", - core_index - )) - })?, + /// Do a 'light weight'(just get references to existing data structures) attach to the core and return relevant debug data. + pub(crate) fn attach_core(&mut self, core_index: usize) -> Result { + if let (Ok(target_core), Some(core_data)) = ( + self.session.core(core_index), + self.core_data + .iter_mut() + .find(|core_data| core_data.core_index == core_index), + ) { + Ok(CoreHandle { + core: target_core, capstone: &self.capstone, - breakpoints: self.breakpoints.get_mut(core_index).ok_or_else(|| { - DebuggerError::Other(anyhow!( - "ActiveBreakpoint cache was not correctly configured for core # {}", - core_index - )) - })?, - rtt_connection: &mut self.rtt_connection, - }), - Err(_) => Err(DebuggerError::UnableToOpenProbe(Some( + core_data, + }) + } else { + Err(DebuggerError::UnableToOpenProbe(Some( "No core at the specified index.", - ))), + ))) + } + } + + /// Check all target cores to ensure they have a configured and initialized RTT connections and if they do, process the RTT data. + /// Return true if at least one channel on one core had data in the buffer. + pub(crate) fn poll_rtt( + &mut self, + session_config: &SessionConfig, + debug_adapter: &mut DebugAdapter

, + ) -> bool { + let mut at_least_one_channel_had_data = false; + for core_config in session_config.core_configs.iter() { + if core_config.rtt_config.enabled { + let target_memory_map = self.session.target().memory_map.clone(); + if let Ok(mut target_core) = self.attach_core(core_config.core_index) { + if let Some(core_rtt) = &mut target_core.core_data.rtt_connection { + // We should poll the target for rtt data. + at_least_one_channel_had_data = at_least_one_channel_had_data + | core_rtt.process_rtt_data(debug_adapter, &mut target_core.core); + } else { + // We have not yet reached the point in the target application where the RTT buffers are initialized, so let's check again. + if debug_adapter.last_known_status != CoreStatus::Unknown + // Do not attempt this until we have processed the MSDAP request for "configurationDone" ... + { + #[allow(clippy::unwrap_used)] + match target_core.attach_to_rtt( + debug_adapter, + &target_memory_map, + core_config.program_binary.as_ref().unwrap(), + &core_config.rtt_config, + ) { + Ok(_) => { + // Nothing else to do. + } + Err(error) => { + debug_adapter + .send_error_response(&DebuggerError::Other(error)) + .ok(); + } + } + } + } + } else { + log::debug!( + "Failed to attach to target core #{}. Cannot poll for RTT data.", + core_config.core_index + ); + } + } else { + // No RTT configured. + } } + at_least_one_channel_had_data } } From 503cad13838960ebdbcb0b2722a7bacb9c0d8622 Mon Sep 17 00:00:00 2001 From: JackN Date: Mon, 21 Mar 2022 17:07:20 -0400 Subject: [PATCH 05/29] Load CMSIS-SVD file --- debugger/Cargo.toml | 2 ++ debugger/src/debug_adapter/dap_adapter.rs | 16 ++++++++++ debugger/src/debugger/core_data.rs | 3 +- debugger/src/debugger/session_data.rs | 39 +++++++++++++++++------ debugger/src/main.rs | 1 + debugger/src/peripherals/mod.rs | 9 ++++++ debugger/src/peripherals/svd_variables.rs | 18 +++++++++++ 7 files changed, 78 insertions(+), 10 deletions(-) create mode 100644 debugger/src/peripherals/mod.rs create mode 100644 debugger/src/peripherals/svd_variables.rs diff --git a/debugger/Cargo.toml b/debugger/Cargo.toml index d540012821..962dd71589 100644 --- a/debugger/Cargo.toml +++ b/debugger/Cargo.toml @@ -39,6 +39,8 @@ schemafy = "^0.6" chrono = { version = "0.4", features = ["serde"] } goblin = "0.5.1" base64 = "0.13" +svd-parser = "0.13.1" +svd-rs = "0.13.1" [dev-dependencies] insta = "1.8.0" diff --git a/debugger/src/debug_adapter/dap_adapter.rs b/debugger/src/debug_adapter/dap_adapter.rs index 2a81156cc8..626d1f89b9 100644 --- a/debugger/src/debug_adapter/dap_adapter.rs +++ b/debugger/src/debug_adapter/dap_adapter.rs @@ -1191,6 +1191,22 @@ impl DebugAdapter

{ variables_reference: locals_root_variable.variable_key, }); }; + + if let Some(core_peripherals) = &target_core.core_data.core_peripherals { + dap_scopes.push(Scope { + line: None, + column: None, + end_column: None, + end_line: None, + expensive: true, // VSCode won't open this tree by default. + indexed_variables: None, + name: "Peripherals".to_string(), + presentation_hint: Some("registers".to_string()), + named_variables: None, + source: None, + variables_reference: core_peripherals.id, + }); + }; } self.send_response(request, Ok(Some(ScopesResponseBody { scopes: dap_scopes }))) diff --git a/debugger/src/debugger/core_data.rs b/debugger/src/debugger/core_data.rs index 9c5a330bdb..bdb2a9bbca 100644 --- a/debugger/src/debugger/core_data.rs +++ b/debugger/src/debugger/core_data.rs @@ -2,6 +2,7 @@ use super::session_data; use crate::{ debug_adapter::{dap_adapter::DebugAdapter, protocol::ProtocolAdapter}, debugger::debug_rtt, + peripherals::svd_variables::SvdCache, DebuggerError, }; use anyhow::Result; @@ -14,7 +15,7 @@ pub struct CoreData { pub(crate) core_index: usize, pub(crate) target_name: String, pub(crate) debug_info: DebugInfo, - pub(crate) core_peripherals: Option, + pub(crate) core_peripherals: Option, pub(crate) stack_frames: Vec, pub(crate) breakpoints: Vec, pub(crate) rtt_connection: Option, diff --git a/debugger/src/debugger/session_data.rs b/debugger/src/debugger/session_data.rs index acc2b1731c..c6d555af99 100644 --- a/debugger/src/debugger/session_data.rs +++ b/debugger/src/debugger/session_data.rs @@ -2,7 +2,7 @@ use super::configuration::{self, CoreConfig, SessionConfig}; use super::core_data::{CoreData, CoreHandle}; use crate::debug_adapter::dap_adapter::DebugAdapter; use crate::debug_adapter::protocol::ProtocolAdapter; -use crate::DebuggerError; +use crate::{debug_adapter, DebuggerError}; use anyhow::{anyhow, Result}; use capstone::Endian; use capstone::{ @@ -16,7 +16,8 @@ use probe_rs::Probe; use probe_rs::ProbeCreationError; use probe_rs::Session; use probe_rs::{CoreStatus, DebugProbeError}; -use std::env::set_current_dir; +use std::{env::set_current_dir, fs::File, io::Read}; +use svd_parser as svd; /// The supported breakpoint types #[derive(Debug, PartialEq)] @@ -190,16 +191,36 @@ impl SessionData { // Configure the [CorePeripherals]. let core_peripherals = if let Some(svd_file) = &core_configuration.svd_file { - // TODO: To be implemented. - None + let mut svd_xml = &mut String::new(); + match File::open(svd_file.as_path()) { + Ok(mut svd_opened_file) => { + svd_opened_file.read_to_string(svd_xml); + match svd::parse(&svd_xml) { + Ok(peripheral_device) => { + Some(crate::peripherals::svd_variables::SvdCache { + id: probe_rs::debug::get_sequential_key(), + svd_device: peripheral_device, + svd_registers: probe_rs::debug::VariableCache::new(), + }) + } + Err(error) => { + log::error!( + "Unable to parse CMSIS-SVD file: {:?}. {:?}", + svd_file, + error + ); + None + } + } + } + Err(error) => { + log::error!("{}", error); + None + } + } } else { // Loading core_peripherals from a CMSIS-SVD file is optional. None - // return Err(anyhow!( - // "Please provide a valid `program_binary` for debug core: {:?}", - // core_configuration.core_index - // ) - // .into()); }; core_data_vec.push(CoreData { diff --git a/debugger/src/main.rs b/debugger/src/main.rs index 0814734449..8f2b90d32a 100644 --- a/debugger/src/main.rs +++ b/debugger/src/main.rs @@ -3,6 +3,7 @@ // Uses Schemafy to generate DAP types from Json mod debug_adapter; mod debugger; +mod peripherals; use anyhow::Result; use clap::{crate_authors, crate_description, crate_name, crate_version, Parser}; diff --git a/debugger/src/peripherals/mod.rs b/debugger/src/peripherals/mod.rs new file mode 100644 index 0000000000..7630c702c3 --- /dev/null +++ b/debugger/src/peripherals/mod.rs @@ -0,0 +1,9 @@ +/// Notes about SVD: +/// Peripherals: +/// - Are 'grouped', but many only belong to a single group. +/// - The 'derived from' properties need to be read first, then overlay the specified properties +/// Start off with everything being read-only. +/// We only have to build the structure once down to 'fields' level. +/// Fields need to be read every stacktrace, because they will change value. +/// Once an SVD file has been parsed, it's structure is loaded as a hierarchical set of variables. +pub(crate) mod svd_variables; diff --git a/debugger/src/peripherals/svd_variables.rs b/debugger/src/peripherals/svd_variables.rs new file mode 100644 index 0000000000..8b39b87ed9 --- /dev/null +++ b/debugger/src/peripherals/svd_variables.rs @@ -0,0 +1,18 @@ +use std::fmt::Debug; + +use crate::DebuggerError; +use probe_rs::debug::VariableCache; +use svd_rs::{peripheral, Device, MaybeArray::Single, PeripheralInfo}; + +/// The SVD file contents and related data +#[derive(Debug)] +pub(crate) struct SvdCache { + /// A unique identifier + pub(crate) id: i64, + /// The Device file represents the top element in a SVD file. + pub(crate) svd_device: Device, + /// The SVD contents and structure will be stored as variables, down to the Register level. + /// Unlike other VariableCache instances, it will only be built once per DebugSession. + /// After that, only the SVD fields change values, and the data for these will be re-read everytime they are queried by the debugger. + pub(crate) svd_registers: VariableCache, +} From 36d6c86164f799cceadd2c043beed880125631c0 Mon Sep 17 00:00:00 2001 From: JackN Date: Wed, 23 Mar 2022 13:28:11 -0400 Subject: [PATCH 06/29] SVD: Resolve 'derived_from` objects --- debugger/src/debug_adapter/dap_adapter.rs | 2 +- debugger/src/debugger/session_data.rs | 58 ++--- debugger/src/peripherals/svd_variables.rs | 295 +++++++++++++++++++++- 3 files changed, 310 insertions(+), 45 deletions(-) diff --git a/debugger/src/debug_adapter/dap_adapter.rs b/debugger/src/debug_adapter/dap_adapter.rs index 626d1f89b9..66c9fd3b7b 100644 --- a/debugger/src/debug_adapter/dap_adapter.rs +++ b/debugger/src/debug_adapter/dap_adapter.rs @@ -1192,7 +1192,7 @@ impl DebugAdapter

{ }); }; - if let Some(core_peripherals) = &target_core.core_data.core_peripherals { + if let Some(core_peripherals) = &mut target_core.core_data.core_peripherals { dap_scopes.push(Scope { line: None, column: None, diff --git a/debugger/src/debugger/session_data.rs b/debugger/src/debugger/session_data.rs index c6d555af99..53d630cbd8 100644 --- a/debugger/src/debugger/session_data.rs +++ b/debugger/src/debugger/session_data.rs @@ -1,23 +1,22 @@ -use super::configuration::{self, CoreConfig, SessionConfig}; -use super::core_data::{CoreData, CoreHandle}; -use crate::debug_adapter::dap_adapter::DebugAdapter; -use crate::debug_adapter::protocol::ProtocolAdapter; -use crate::{debug_adapter, DebuggerError}; +use super::{ + configuration::{self, CoreConfig, SessionConfig}, + core_data::{CoreData, CoreHandle}, +}; +use crate::{ + debug_adapter::{dap_adapter::DebugAdapter, protocol::ProtocolAdapter}, + peripherals::svd_variables::SvdCache, + DebuggerError, +}; use anyhow::{anyhow, Result}; -use capstone::Endian; use capstone::{ arch::arm::ArchMode as armArchMode, arch::riscv::ArchMode as riscvArchMode, prelude::*, - Capstone, + Capstone, Endian, }; -use probe_rs::config::TargetSelector; -use probe_rs::debug::DebugInfo; -use probe_rs::Permissions; -use probe_rs::Probe; -use probe_rs::ProbeCreationError; -use probe_rs::Session; -use probe_rs::{CoreStatus, DebugProbeError}; -use std::{env::set_current_dir, fs::File, io::Read}; -use svd_parser as svd; +use probe_rs::{ + config::TargetSelector, debug::DebugInfo, CoreStatus, DebugProbeError, Permissions, Probe, + ProbeCreationError, Session, +}; +use std::env::set_current_dir; /// The supported breakpoint types #[derive(Debug, PartialEq)] @@ -190,31 +189,12 @@ impl SessionData { }; // Configure the [CorePeripherals]. + // TODO: Implement progress reporting for this operation. let core_peripherals = if let Some(svd_file) = &core_configuration.svd_file { - let mut svd_xml = &mut String::new(); - match File::open(svd_file.as_path()) { - Ok(mut svd_opened_file) => { - svd_opened_file.read_to_string(svd_xml); - match svd::parse(&svd_xml) { - Ok(peripheral_device) => { - Some(crate::peripherals::svd_variables::SvdCache { - id: probe_rs::debug::get_sequential_key(), - svd_device: peripheral_device, - svd_registers: probe_rs::debug::VariableCache::new(), - }) - } - Err(error) => { - log::error!( - "Unable to parse CMSIS-SVD file: {:?}. {:?}", - svd_file, - error - ); - None - } - } - } + match SvdCache::new(svd_file) { + Ok(core_peripherals) => Some(core_peripherals), Err(error) => { - log::error!("{}", error); + log::error!("{:?}", error); None } } diff --git a/debugger/src/peripherals/svd_variables.rs b/debugger/src/peripherals/svd_variables.rs index 8b39b87ed9..3cf871aa5b 100644 --- a/debugger/src/peripherals/svd_variables.rs +++ b/debugger/src/peripherals/svd_variables.rs @@ -1,18 +1,303 @@ -use std::fmt::Debug; - use crate::DebuggerError; use probe_rs::debug::VariableCache; -use svd_rs::{peripheral, Device, MaybeArray::Single, PeripheralInfo}; +use std::{any, fmt::Debug, fs::File, io::Read, path::PathBuf}; +use svd_parser::{self as svd, ValidateLevel}; +use svd_rs::{Device, EnumeratedValues, FieldInfo, PeripheralInfo, RegisterInfo}; /// The SVD file contents and related data #[derive(Debug)] pub(crate) struct SvdCache { /// A unique identifier pub(crate) id: i64, - /// The Device file represents the top element in a SVD file. - pub(crate) svd_device: Device, /// The SVD contents and structure will be stored as variables, down to the Register level. /// Unlike other VariableCache instances, it will only be built once per DebugSession. /// After that, only the SVD fields change values, and the data for these will be re-read everytime they are queried by the debugger. pub(crate) svd_registers: VariableCache, } + +impl SvdCache { + /// Create the SVD cache for a specific core. This function loads the file, parses it, and then builds the VariableCache. + pub(crate) fn new(svd_file: &PathBuf) -> Result { + let svd_xml = &mut String::new(); + match File::open(svd_file.as_path()) { + Ok(mut svd_opened_file) => { + svd_opened_file.read_to_string(svd_xml); + match svd::parse(&svd_xml) { + Ok(peripheral_device) => Ok(SvdCache { + id: probe_rs::debug::get_sequential_key(), + svd_registers: variable_cache_from_svd(peripheral_device), + }), + Err(error) => Err(DebuggerError::Other(anyhow::anyhow!( + "Unable to parse CMSIS-SVD file: {:?}. {:?}", + svd_file, + error, + ))), + } + } + Err(error) => Err(DebuggerError::Other(anyhow::anyhow!("{}", error))), + } + } +} + +/// Create a [`probe_rs::debug::VariableCache`] from a Device that was parsed from a CMSIS-SVD file. +pub(crate) fn variable_cache_from_svd(peripheral_device: Device) -> probe_rs::debug::VariableCache { + probe_rs::debug::VariableCache::new() +} + +/// Resolve all the peripherals through their (optional) `derived_from` peripheral. +pub(crate) fn resolve_peripherals( + peripheral_device: &Device, +) -> Result, DebuggerError> { + let mut resolved_peripherals = vec![]; + for device_peripheral in &peripheral_device.peripherals { + // TODO: Need to code for the impact of MaybeArray results. + let mut peripheral_builder = PeripheralInfo::builder(); + if let Some(derived_from) = &device_peripheral.derived_from { + if let Some(template_peripheral) = peripheral_device.get_peripheral(derived_from) { + if template_peripheral.group_name.is_some() { + peripheral_builder = + peripheral_builder.group_name(template_peripheral.group_name.clone()); + } + if template_peripheral.prepend_to_name.is_some() { + peripheral_builder = peripheral_builder + .prepend_to_name(template_peripheral.prepend_to_name.clone()); + } + if template_peripheral.append_to_name.is_some() { + peripheral_builder = peripheral_builder + .append_to_name(template_peripheral.append_to_name.clone()); + } + peripheral_builder = + peripheral_builder.base_address(template_peripheral.base_address); + peripheral_builder = peripheral_builder + .default_register_properties(template_peripheral.default_register_properties); + if template_peripheral.address_block.is_some() { + peripheral_builder = + peripheral_builder.address_block(template_peripheral.address_block.clone()); + } + peripheral_builder = + peripheral_builder.interrupt(Some(template_peripheral.interrupt.clone())); + if template_peripheral.registers.is_some() { + peripheral_builder = + peripheral_builder.registers(template_peripheral.registers.clone()); + } + } else { + return Err(DebuggerError::Other(anyhow::anyhow!( + "Unable to retrieve 'derived_from' SVD peripheral: {:?}", + derived_from + ))); + }; + } + // Irrespective of derived_from values, set the values we need. + peripheral_builder = peripheral_builder.name(device_peripheral.name.clone()); + peripheral_builder = peripheral_builder.description(device_peripheral.description.clone()); + if device_peripheral.group_name.is_some() { + peripheral_builder = + peripheral_builder.group_name(device_peripheral.group_name.clone()); + } + if device_peripheral.prepend_to_name.is_some() { + peripheral_builder = + peripheral_builder.prepend_to_name(device_peripheral.prepend_to_name.clone()); + } + if device_peripheral.append_to_name.is_some() { + peripheral_builder = + peripheral_builder.append_to_name(device_peripheral.append_to_name.clone()); + } + peripheral_builder = peripheral_builder.base_address(device_peripheral.base_address); + peripheral_builder = peripheral_builder + .default_register_properties(device_peripheral.default_register_properties); + if device_peripheral.address_block.is_some() { + peripheral_builder = + peripheral_builder.address_block(device_peripheral.address_block.clone()); + } + peripheral_builder = + peripheral_builder.interrupt(Some(device_peripheral.interrupt.clone())); + if device_peripheral.registers.is_some() { + peripheral_builder = peripheral_builder.registers(device_peripheral.registers.clone()); + } + let resolved_peripheral = peripheral_builder + .build(ValidateLevel::Weak) + .map_err(|error| DebuggerError::Other(anyhow::anyhow!("{:?}", error)))?; + resolved_peripherals.push(resolved_peripheral); + } + Ok(resolved_peripherals) +} + +/// Resolve all the registers of a peripheral through their (optional) `derived_from` register. +pub(crate) fn resolve_registers( + peripheral: PeripheralInfo, +) -> Result, DebuggerError> { + // TODO: Need to code for the impact of register clusters. + let mut resolved_registers = vec![]; + for peripheral_register in peripheral.registers() { + // TODO: Need to code for the impact of MaybeArray results. + let mut register_builder = RegisterInfo::builder(); + if let Some(derived_from) = &peripheral_register.derived_from { + if let Some(template_register) = peripheral.get_register(derived_from) { + if template_register.display_name.is_some() { + register_builder = + register_builder.display_name(template_register.display_name.clone()); + } + if template_register.description.is_some() { + register_builder = + register_builder.description(template_register.description.clone()); + } + if template_register.modified_write_values.is_some() { + register_builder = register_builder + .modified_write_values(template_register.modified_write_values.clone()); + } + if template_register.write_constraint.is_some() { + register_builder = register_builder + .write_constraint(template_register.write_constraint.clone()); + } + if template_register.read_action.is_some() { + register_builder = + register_builder.read_action(template_register.read_action.clone()); + } + if template_register.fields.is_some() { + register_builder = register_builder.fields(template_register.fields.clone()); + } + } else { + return Err(DebuggerError::Other(anyhow::anyhow!( + "Unable to retrieve 'derived_from' SVD register: {:?}", + derived_from + ))); + }; + } + // Irrespective of derived_from values, set the values we need. + register_builder = register_builder.name(peripheral_register.name.clone()); + if peripheral_register.display_name.is_some() { + register_builder = + register_builder.display_name(peripheral_register.display_name.clone()); + } + if peripheral_register.description.is_some() { + register_builder = + register_builder.description(peripheral_register.description.clone()); + } + register_builder = + register_builder.address_offset(peripheral_register.address_offset.clone()); + register_builder = register_builder.properties(peripheral_register.properties.clone()); + if peripheral_register.modified_write_values.is_some() { + register_builder = register_builder + .modified_write_values(peripheral_register.modified_write_values.clone()); + } + if peripheral_register.write_constraint.is_some() { + register_builder = + register_builder.write_constraint(peripheral_register.write_constraint.clone()); + } + if peripheral_register.read_action.is_some() { + register_builder = + register_builder.read_action(peripheral_register.read_action.clone()); + } + if peripheral_register.fields.is_some() { + register_builder = register_builder.fields(peripheral_register.fields.clone()); + } + let resolved_register = register_builder + .build(ValidateLevel::Weak) + .map_err(|error| DebuggerError::Other(anyhow::anyhow!("{:?}", error)))?; + resolved_registers.push(resolved_register); + } + Ok(resolved_registers) +} + +/// Resolve all the fields of a register through their (optional) `derived_from` field. +pub(crate) fn resolve_fields(register: RegisterInfo) -> Result, DebuggerError> { + // TODO: Need to code for the impact of field clusters. + let mut resolved_fields = vec![]; + for register_field in register.fields() { + // TODO: Need to code for the impact of MaybeArray results. + let mut field_builder = FieldInfo::builder(); + if let Some(derived_from) = ®ister_field.derived_from { + if let Some(template_field) = register.get_field(derived_from) { + if template_field.description.is_some() { + field_builder = field_builder.description(template_field.description.clone()); + } + if template_field.access.is_some() { + field_builder = field_builder.access(template_field.access.clone()); + } + if template_field.modified_write_values.is_some() { + field_builder = field_builder + .modified_write_values(template_field.modified_write_values.clone()); + } + if template_field.write_constraint.is_some() { + field_builder = + field_builder.write_constraint(template_field.write_constraint.clone()); + } + if template_field.read_action.is_some() { + field_builder = field_builder.read_action(template_field.read_action.clone()); + } + } else { + return Err(DebuggerError::Other(anyhow::anyhow!( + "Unable to retrieve 'derived_from' SVD field: {:?}", + derived_from + ))); + }; + } + // Irrespective of derived_from values, set the values we need. + field_builder = field_builder.name(register_field.name.clone()); + if register_field.description.is_some() { + field_builder = field_builder.description(register_field.description.clone()); + } + field_builder = field_builder.bit_range(register_field.bit_range.clone()); + field_builder = field_builder.access(register_field.access.clone()); + if register_field.modified_write_values.is_some() { + field_builder = + field_builder.modified_write_values(register_field.modified_write_values.clone()); + } + if register_field.write_constraint.is_some() { + field_builder = field_builder.write_constraint(register_field.write_constraint.clone()); + } + if register_field.read_action.is_some() { + field_builder = field_builder.read_action(register_field.read_action.clone()); + } + field_builder = field_builder.enumerated_values(register_field.enumerated_values.clone()); + let resolved_field = field_builder + .build(ValidateLevel::Weak) + .map_err(|error| DebuggerError::Other(anyhow::anyhow!("{:?}", error)))?; + resolved_fields.push(resolved_field); + } + Ok(resolved_fields) +} + +/// Resolve all the enumerated values of a field through their (optional) `derived_from` values. +pub(crate) fn enumerated_values(field: FieldInfo) -> Result, DebuggerError> { + // TODO: Need to code for the impact of enumerated value clusters. + let mut enumerated_values = vec![]; + for field_enum_values in &field.enumerated_values { + // TODO: Need to code for the impact of MaybeArray results. + let mut enum_values_builder = EnumeratedValues::builder(); + if let Some(derived_from) = &field_enum_values.derived_from { + if let Some(template_enum_values) = + field.enumerated_values.iter().find(|derived_from_values| { + derived_from_values.name == Some(derived_from.to_owned()) + }) + { + if template_enum_values.name.is_some() { + enum_values_builder = + enum_values_builder.name(template_enum_values.name.clone()); + } + if template_enum_values.usage.is_some() { + enum_values_builder = + enum_values_builder.usage(template_enum_values.usage.clone()); + } + } else { + return Err(DebuggerError::Other(anyhow::anyhow!( + "Unable to retrieve 'derived_from' SVD field: {:?}", + derived_from + ))); + }; + } + // Irrespective of derived_from values, set the values we need. + if field_enum_values.name.is_some() { + enum_values_builder = enum_values_builder.name(field_enum_values.name.clone()); + } + if field_enum_values.usage.is_some() { + enum_values_builder = enum_values_builder.usage(field_enum_values.usage.clone()); + } + enum_values_builder = enum_values_builder.values(field_enum_values.values.clone()); + let resolved_enum_values = enum_values_builder + .build(ValidateLevel::Weak) + .map_err(|error| DebuggerError::Other(anyhow::anyhow!("{:?}", error)))?; + enumerated_values.push(resolved_enum_values); + } + Ok(enumerated_values) +} From db863fbb52e654e3c6c55dbbfc5fa3a6d41ce3c0 Mon Sep 17 00:00:00 2001 From: JackN Date: Wed, 23 Mar 2022 18:52:22 -0400 Subject: [PATCH 07/29] SVD Peripheral structure as DAP Variables --- debugger/src/debug_adapter/dap_adapter.rs | 112 +++++++++++++++------- debugger/src/debugger/debug_entry.rs | 14 +++ debugger/src/debugger/session_data.rs | 17 +--- debugger/src/peripherals/svd_variables.rs | 94 +++++++++++++++--- probe-rs/src/debug/mod.rs | 4 +- probe-rs/src/debug/variable.rs | 5 +- 6 files changed, 180 insertions(+), 66 deletions(-) diff --git a/debugger/src/debug_adapter/dap_adapter.rs b/debugger/src/debug_adapter/dap_adapter.rs index 66c9fd3b7b..27d5fcf4ec 100644 --- a/debugger/src/debug_adapter/dap_adapter.rs +++ b/debugger/src/debug_adapter/dap_adapter.rs @@ -1118,17 +1118,10 @@ impl DebugAdapter

{ let mut dap_scopes: Vec = vec![]; - log::trace!("Getting scopes for frame {}", arguments.frame_id,); - - if let Some(stack_frame) = target_core.get_stackframe(arguments.frame_id) { - if let Some(static_root_variable) = - stack_frame - .static_variables - .as_ref() - .and_then(|stack_frame| { - stack_frame - .get_variable_by_name_and_parent(&VariableName::StaticScopeRoot, None) - }) + if let Some(core_peripherals) = &mut target_core.core_data.core_peripherals { + if let Some(peripherals_root_variable) = core_peripherals + .svd_variable_cache + .get_variable_by_name_and_parent(&VariableName::PeripheralScopeRoot, None) { dap_scopes.push(Scope { line: None, @@ -1137,14 +1130,18 @@ impl DebugAdapter

{ end_line: None, expensive: true, // VSCode won't open this tree by default. indexed_variables: None, - name: "Static".to_string(), - presentation_hint: Some("statics".to_string()), + name: "Peripherals".to_string(), + presentation_hint: Some("registers".to_string()), named_variables: None, source: None, - variables_reference: static_root_variable.variable_key, + variables_reference: peripherals_root_variable.variable_key, }); - }; + } + }; + log::trace!("Getting scopes for frame {}", arguments.frame_id,); + + if let Some(stack_frame) = target_core.get_stackframe(arguments.frame_id) { dap_scopes.push(Scope { line: None, column: None, @@ -1160,6 +1157,30 @@ impl DebugAdapter

{ variables_reference: stack_frame.id, }); + if let Some(static_root_variable) = + stack_frame + .static_variables + .as_ref() + .and_then(|stack_frame| { + stack_frame + .get_variable_by_name_and_parent(&VariableName::StaticScopeRoot, None) + }) + { + dap_scopes.push(Scope { + line: None, + column: None, + end_column: None, + end_line: None, + expensive: true, // VSCode won't open this tree by default. + indexed_variables: None, + name: "Static".to_string(), + presentation_hint: Some("statics".to_string()), + named_variables: None, + source: None, + variables_reference: static_root_variable.variable_key, + }); + }; + if let Some(locals_root_variable) = stack_frame .local_variables @@ -1191,24 +1212,7 @@ impl DebugAdapter

{ variables_reference: locals_root_variable.variable_key, }); }; - - if let Some(core_peripherals) = &mut target_core.core_data.core_peripherals { - dap_scopes.push(Scope { - line: None, - column: None, - end_column: None, - end_line: None, - expensive: true, // VSCode won't open this tree by default. - indexed_variables: None, - name: "Peripherals".to_string(), - presentation_hint: Some("registers".to_string()), - named_variables: None, - source: None, - variables_reference: core_peripherals.id, - }); - }; } - self.send_response(request, Ok(Some(ScopesResponseBody { scopes: dap_scopes }))) } @@ -1426,6 +1430,7 @@ impl DebugAdapter

{ } } + /// The MS DAP Specification only gives us the unique reference of the variable, and does not tell us which StackFrame it belongs to, nor does it specify if this variable is in the local, register or static scope. Unfortunately this means we have to search through all the available [VariableCache]'s until we find it. To minimize the impact of this, we will search in the most 'likely' places first (first stack frame's locals, then statics, then registers, then move to next stack frame, and so on ...) pub(crate) fn variables( &mut self, target_core: &mut CoreHandle, @@ -1436,8 +1441,49 @@ impl DebugAdapter

{ Err(error) => return self.send_response::<()>(request, Err(error)), }; + if let Some(core_peripherals) = &mut target_core.core_data.core_peripherals { + // First we check the SVD VariableCache, we do this first because it is the lowest computational overhead. + if let Some(search_variable) = core_peripherals + .svd_variable_cache + .get_variable_by_key(arguments.variables_reference) + { + let dap_variables: Vec = core_peripherals + .svd_variable_cache + .get_children(Some(arguments.variables_reference))? + .iter() + // Convert the `probe_rs::debug::Variable` to `probe_rs_debugger::dap_types::Variable` + .map(|variable| { + let ( + variables_reference, + named_child_variables_cnt, + indexed_child_variables_cnt, + ) = self.get_variable_reference( + variable, + &mut core_peripherals.svd_variable_cache, + ); + Variable { + name: variable.name.to_string(), + evaluate_name: Some(variable.name.to_string()), + memory_reference: Some(format!("{:#010x}", variable.memory_location)), + indexed_variables: Some(indexed_child_variables_cnt), + named_variables: Some(named_child_variables_cnt), + presentation_hint: None, + type_: Some(variable.type_name.clone()), + value: variable.get_value(&core_peripherals.svd_variable_cache), + variables_reference, + } + }) + .collect(); + return self.send_response( + request, + Ok(Some(VariablesResponseBody { + variables: dap_variables, + })), + ); + } + } + let response = { - // The MS DAP Specification only gives us the unique reference of the variable, and does not tell us which StackFrame it belongs to, nor does it specify if this variable is in the local, register or static scope. Unfortunately this means we have to search through all the available [VariableCache]'s until we find it. To minimize the impact of this, we will search in the most 'likely' places first (first stack frame's locals, then statics, then registers, then move to next stack frame, and so on ...) let mut parent_variable: Option = None; let mut variable_cache: Option<&mut VariableCache> = None; let mut stack_frame_registers: Option<&Registers> = None; diff --git a/debugger/src/debugger/debug_entry.rs b/debugger/src/debugger/debug_entry.rs index c3705b662c..0c46497a02 100644 --- a/debugger/src/debugger/debug_entry.rs +++ b/debugger/src/debugger/debug_entry.rs @@ -6,6 +6,7 @@ use crate::{ protocol::{DapAdapter, ProtocolAdapter}, }, debugger::configuration::{self, ConsoleLog}, + peripherals::svd_variables::SvdCache, DebuggerError, }; use anyhow::{anyhow, Context, Result}; @@ -836,6 +837,19 @@ impl Debugger { return Err(error); } } + // Before we complete, load the (optional) CMSIS-SVD file and its variable cache. + // Configure the [CorePeripherals]. + // TODO: Implement progress reporting for this operation. + if let Some(svd_file) = &target_core_config.svd_file { + target_core.core_data.core_peripherals = + match SvdCache::new(svd_file, &mut target_core.core) { + Ok(core_peripherals) => Some(core_peripherals), + Err(error) => { + log::error!("{:?}", error); + None + } + }; + } target_core } Err(error) => { diff --git a/debugger/src/debugger/session_data.rs b/debugger/src/debugger/session_data.rs index 53d630cbd8..32c46f3581 100644 --- a/debugger/src/debugger/session_data.rs +++ b/debugger/src/debugger/session_data.rs @@ -188,21 +188,6 @@ impl SessionData { .into()); }; - // Configure the [CorePeripherals]. - // TODO: Implement progress reporting for this operation. - let core_peripherals = if let Some(svd_file) = &core_configuration.svd_file { - match SvdCache::new(svd_file) { - Ok(core_peripherals) => Some(core_peripherals), - Err(error) => { - log::error!("{:?}", error); - None - } - } - } else { - // Loading core_peripherals from a CMSIS-SVD file is optional. - None - }; - core_data_vec.push(CoreData { core_index: core_configuration.core_index, target_name: format!( @@ -211,7 +196,7 @@ impl SessionData { target_session.target().name ), debug_info, - core_peripherals, + core_peripherals: None, stack_frames: Vec::::new(), breakpoints: Vec::::new(), rtt_connection: None, diff --git a/debugger/src/peripherals/svd_variables.rs b/debugger/src/peripherals/svd_variables.rs index 3cf871aa5b..f4eb2bffa0 100644 --- a/debugger/src/peripherals/svd_variables.rs +++ b/debugger/src/peripherals/svd_variables.rs @@ -1,31 +1,31 @@ use crate::DebuggerError; -use probe_rs::debug::VariableCache; +use probe_rs::{ + debug::{DebugError, Variable, VariableCache, VariableName}, + Core, +}; use std::{any, fmt::Debug, fs::File, io::Read, path::PathBuf}; use svd_parser::{self as svd, ValidateLevel}; -use svd_rs::{Device, EnumeratedValues, FieldInfo, PeripheralInfo, RegisterInfo}; +use svd_rs::{peripheral, Device, EnumeratedValues, FieldInfo, PeripheralInfo, RegisterInfo}; /// The SVD file contents and related data #[derive(Debug)] pub(crate) struct SvdCache { - /// A unique identifier - pub(crate) id: i64, - /// The SVD contents and structure will be stored as variables, down to the Register level. + /// The SVD contents and structure will be stored as variables, down to the Field level. /// Unlike other VariableCache instances, it will only be built once per DebugSession. - /// After that, only the SVD fields change values, and the data for these will be re-read everytime they are queried by the debugger. - pub(crate) svd_registers: VariableCache, + /// After that, only the SVD fields values change values, and the data for these will be re-read everytime they are queried by the debugger. + pub(crate) svd_variable_cache: VariableCache, } impl SvdCache { /// Create the SVD cache for a specific core. This function loads the file, parses it, and then builds the VariableCache. - pub(crate) fn new(svd_file: &PathBuf) -> Result { + pub(crate) fn new(svd_file: &PathBuf, core: &mut Core) -> Result { let svd_xml = &mut String::new(); match File::open(svd_file.as_path()) { Ok(mut svd_opened_file) => { svd_opened_file.read_to_string(svd_xml); match svd::parse(&svd_xml) { Ok(peripheral_device) => Ok(SvdCache { - id: probe_rs::debug::get_sequential_key(), - svd_registers: variable_cache_from_svd(peripheral_device), + svd_variable_cache: variable_cache_from_svd(peripheral_device, core)?, }), Err(error) => Err(DebuggerError::Other(anyhow::anyhow!( "Unable to parse CMSIS-SVD file: {:?}. {:?}", @@ -40,8 +40,58 @@ impl SvdCache { } /// Create a [`probe_rs::debug::VariableCache`] from a Device that was parsed from a CMSIS-SVD file. -pub(crate) fn variable_cache_from_svd(peripheral_device: Device) -> probe_rs::debug::VariableCache { - probe_rs::debug::VariableCache::new() +pub(crate) fn variable_cache_from_svd( + peripheral_device: Device, + core: &mut Core, +) -> Result { + let mut svd_cache = probe_rs::debug::VariableCache::new(); + let mut device_root_variable = Variable::new(None, None); + device_root_variable.variable_node_type = probe_rs::debug::VariableNodeType::DoNotRecurse; + device_root_variable.name = VariableName::PeripheralScopeRoot; + device_root_variable = svd_cache.cache_variable(None, device_root_variable, core)?; + for peripheral in &resolve_peripherals(&peripheral_device)? { + // TODO: Create a parent structure for peripheral groups with more than one member. + let mut peripheral_variable = Variable::new(None, None); + peripheral_variable.name = VariableName::Named(peripheral.name.clone()); + peripheral_variable.type_name = "SvdPeripheral".to_string(); + peripheral_variable.variable_node_type = probe_rs::debug::VariableNodeType::DirectLookup; + peripheral_variable.memory_location = peripheral.base_address; + peripheral_variable = svd_cache.cache_variable( + Some(device_root_variable.variable_key), + peripheral_variable, + core, + )?; + for register in &resolve_registers(peripheral)? { + let mut register_variable = Variable::new(None, None); + register_variable.name = VariableName::Named(register.name.clone()); + register_variable.type_name = "SvdRegister".to_string(); + register_variable.variable_node_type = probe_rs::debug::VariableNodeType::DirectLookup; + register_variable.memory_location = + peripheral.base_address + register.address_offset as u64; + register_variable = svd_cache.cache_variable( + Some(peripheral_variable.variable_key), + register_variable, + core, + )?; + for field in &resolve_fields(register)? { + let mut field_variable = Variable::new(None, None); + field_variable.name = VariableName::Named(field.name.clone()); + field_variable.type_name = "SvdField".to_string(); + field_variable.variable_node_type = probe_rs::debug::VariableNodeType::DirectLookup; + field_variable.memory_location = register_variable.memory_location; + // For SVD fields, we overload the range_lower_bound and range_upper_bound as the bit range LSB and MSB. + field_variable.range_lower_bound = field.bit_offset() as i64; + field_variable.range_upper_bound = (field.bit_offset() + field.bit_width()) as i64; + field_variable = svd_cache.cache_variable( + Some(register_variable.variable_key), + field_variable, + core, + )?; + } + } + } + + Ok(svd_cache) } /// Resolve all the peripherals through their (optional) `derived_from` peripheral. @@ -58,6 +108,14 @@ pub(crate) fn resolve_peripherals( peripheral_builder = peripheral_builder.group_name(template_peripheral.group_name.clone()); } + if template_peripheral.display_name.is_some() { + peripheral_builder = + peripheral_builder.display_name(template_peripheral.display_name.clone()); + } + if template_peripheral.description.is_some() { + peripheral_builder = + peripheral_builder.description(template_peripheral.description.clone()); + } if template_peripheral.prepend_to_name.is_some() { peripheral_builder = peripheral_builder .prepend_to_name(template_peripheral.prepend_to_name.clone()); @@ -90,6 +148,14 @@ pub(crate) fn resolve_peripherals( // Irrespective of derived_from values, set the values we need. peripheral_builder = peripheral_builder.name(device_peripheral.name.clone()); peripheral_builder = peripheral_builder.description(device_peripheral.description.clone()); + if device_peripheral.description.is_some() { + peripheral_builder = + peripheral_builder.description(device_peripheral.description.clone()); + } + if device_peripheral.display_name.is_some() { + peripheral_builder = + peripheral_builder.display_name(device_peripheral.display_name.clone()); + } if device_peripheral.group_name.is_some() { peripheral_builder = peripheral_builder.group_name(device_peripheral.group_name.clone()); @@ -124,7 +190,7 @@ pub(crate) fn resolve_peripherals( /// Resolve all the registers of a peripheral through their (optional) `derived_from` register. pub(crate) fn resolve_registers( - peripheral: PeripheralInfo, + peripheral: &PeripheralInfo, ) -> Result, DebuggerError> { // TODO: Need to code for the impact of register clusters. let mut resolved_registers = vec![]; @@ -200,7 +266,7 @@ pub(crate) fn resolve_registers( } /// Resolve all the fields of a register through their (optional) `derived_from` field. -pub(crate) fn resolve_fields(register: RegisterInfo) -> Result, DebuggerError> { +pub(crate) fn resolve_fields(register: &RegisterInfo) -> Result, DebuggerError> { // TODO: Need to code for the impact of field clusters. let mut resolved_fields = vec![]; for register_field in register.fields() { diff --git a/probe-rs/src/debug/mod.rs b/probe-rs/src/debug/mod.rs index fef0a41c47..a84cc68284 100644 --- a/probe-rs/src/debug/mod.rs +++ b/probe-rs/src/debug/mod.rs @@ -17,7 +17,7 @@ use crate::{ }; use num_traits::Zero; use probe_rs_target::Architecture; -pub use variable::{Variable, VariableCache, VariableLocation, VariableName, VariantRole}; +pub use variable::{Variable, VariableCache, VariableName, VariableNodeType, VariantRole}; use std::{ borrow, @@ -36,7 +36,7 @@ use gimli::{ }; use object::read::{Object, ObjectSection}; -use self::variable::{VariableNodeType, VariableValue}; +use self::variable::VariableValue; /// An error occurred while debugging the target. #[derive(Debug, thiserror::Error)] diff --git a/probe-rs/src/debug/variable.rs b/probe-rs/src/debug/variable.rs index d08d18ad50..d43a689970 100644 --- a/probe-rs/src/debug/variable.rs +++ b/probe-rs/src/debug/variable.rs @@ -320,6 +320,8 @@ pub enum VariableName { RegistersRoot, /// Top-level variable for local scoped variables, child of a stack frame variable. LocalScopeRoot, + /// Top-level variable for CMSIS-SVD file Device peripherals/registers/fields. + PeripheralScopeRoot, /// Artificial variable, without a name (e.g. enum discriminant) Artifical, /// Anonymous namespace @@ -344,6 +346,7 @@ impl std::fmt::Display for VariableName { VariableName::StaticScopeRoot => write!(f, "Static Variable"), VariableName::RegistersRoot => write!(f, "Platform Register"), VariableName::LocalScopeRoot => write!(f, "Function Variable"), + VariableName::PeripheralScopeRoot => write!(f, "Peripheral Variable"), VariableName::Artifical => write!(f, ""), VariableName::AnonymousNamespace => write!(f, ""), VariableName::Namespace(name) => name.fmt(f), @@ -551,7 +554,7 @@ pub struct Variable { impl Variable { /// In most cases, Variables will be initialized with their ELF references so that we resolve their data types and values on demand. - pub(crate) fn new( + pub fn new( header_offset: Option, entries_offset: Option, ) -> Variable { From d650bf8a5e78f3b435d50382146388aef54a6f4d Mon Sep 17 00:00:00 2001 From: JackN Date: Thu, 24 Mar 2022 14:48:49 -0400 Subject: [PATCH 08/29] SVD Register and Field values --- debugger/src/debug_adapter/dap_adapter.rs | 11 +++- debugger/src/peripherals/svd_variables.rs | 29 +++++++-- probe-rs/src/debug/mod.rs | 16 ++--- probe-rs/src/debug/variable.rs | 79 ++++++++++++++++++++--- 4 files changed, 110 insertions(+), 25 deletions(-) diff --git a/debugger/src/debug_adapter/dap_adapter.rs b/debugger/src/debug_adapter/dap_adapter.rs index 27d5fcf4ec..998511c464 100644 --- a/debugger/src/debug_adapter/dap_adapter.rs +++ b/debugger/src/debug_adapter/dap_adapter.rs @@ -1450,7 +1450,7 @@ impl DebugAdapter

{ let dap_variables: Vec = core_peripherals .svd_variable_cache .get_children(Some(arguments.variables_reference))? - .iter() + .iter_mut() // Convert the `probe_rs::debug::Variable` to `probe_rs_debugger::dap_types::Variable` .map(|variable| { let ( @@ -1469,7 +1469,14 @@ impl DebugAdapter

{ named_variables: Some(named_child_variables_cnt), presentation_hint: None, type_: Some(variable.type_name.clone()), - value: variable.get_value(&core_peripherals.svd_variable_cache), + value: { + // The SVD cache is not automatically refreshed on every stack trace, and we only need to refresh the field values. + variable.extract_value( + &mut target_core.core, + &core_peripherals.svd_variable_cache, + ); + variable.get_value(&core_peripherals.svd_variable_cache) + }, variables_reference, } }) diff --git a/debugger/src/peripherals/svd_variables.rs b/debugger/src/peripherals/svd_variables.rs index f4eb2bffa0..d60d49c535 100644 --- a/debugger/src/peripherals/svd_variables.rs +++ b/debugger/src/peripherals/svd_variables.rs @@ -53,19 +53,32 @@ pub(crate) fn variable_cache_from_svd( // TODO: Create a parent structure for peripheral groups with more than one member. let mut peripheral_variable = Variable::new(None, None); peripheral_variable.name = VariableName::Named(peripheral.name.clone()); - peripheral_variable.type_name = "SvdPeripheral".to_string(); - peripheral_variable.variable_node_type = probe_rs::debug::VariableNodeType::DirectLookup; + peripheral_variable.type_name = peripheral + .description + .clone() + .unwrap_or_else(|| "Device Peripheral".to_string()); + peripheral_variable.variable_node_type = probe_rs::debug::VariableNodeType::SvdPeripheral; peripheral_variable.memory_location = peripheral.base_address; + peripheral_variable.set_value(probe_rs::debug::VariableValue::Valid( + peripheral + .description + .clone() + .unwrap_or_else(|| format!("{}", peripheral_variable.name)), + )); peripheral_variable = svd_cache.cache_variable( Some(device_root_variable.variable_key), peripheral_variable, core, )?; for register in &resolve_registers(peripheral)? { + // TODO: Implement warnings for users if they are going to read registers that have a side effect on reading. let mut register_variable = Variable::new(None, None); register_variable.name = VariableName::Named(register.name.clone()); - register_variable.type_name = "SvdRegister".to_string(); - register_variable.variable_node_type = probe_rs::debug::VariableNodeType::DirectLookup; + register_variable.type_name = register + .description + .clone() + .unwrap_or_else(|| "Peripheral Register".to_string()); + register_variable.variable_node_type = probe_rs::debug::VariableNodeType::SvdRegister; register_variable.memory_location = peripheral.base_address + register.address_offset as u64; register_variable = svd_cache.cache_variable( @@ -76,12 +89,16 @@ pub(crate) fn variable_cache_from_svd( for field in &resolve_fields(register)? { let mut field_variable = Variable::new(None, None); field_variable.name = VariableName::Named(field.name.clone()); - field_variable.type_name = "SvdField".to_string(); - field_variable.variable_node_type = probe_rs::debug::VariableNodeType::DirectLookup; + field_variable.type_name = field + .description + .clone() + .unwrap_or_else(|| "Register Field".to_string()); + field_variable.variable_node_type = probe_rs::debug::VariableNodeType::SvdField; field_variable.memory_location = register_variable.memory_location; // For SVD fields, we overload the range_lower_bound and range_upper_bound as the bit range LSB and MSB. field_variable.range_lower_bound = field.bit_offset() as i64; field_variable.range_upper_bound = (field.bit_offset() + field.bit_width()) as i64; + // TODO: Extend the Variable definition, so that we can resolve the EnumeratedValues for fields. field_variable = svd_cache.cache_variable( Some(register_variable.variable_key), field_variable, diff --git a/probe-rs/src/debug/mod.rs b/probe-rs/src/debug/mod.rs index a84cc68284..9bc8f5fe3d 100644 --- a/probe-rs/src/debug/mod.rs +++ b/probe-rs/src/debug/mod.rs @@ -15,10 +15,12 @@ use crate::{ debug::variable::VariableType, CoreStatus, MemoryInterface, }; +use gimli::{ + DebuggingInformationEntry, FileEntry, LineProgramHeader, Location, UnitOffset, UnwindContext, +}; use num_traits::Zero; +use object::read::{Object, ObjectSection}; use probe_rs_target::Architecture; -pub use variable::{Variable, VariableCache, VariableName, VariableNodeType, VariantRole}; - use std::{ borrow, collections::HashMap, @@ -30,13 +32,9 @@ use std::{ sync::atomic::{AtomicI64, Ordering}, vec, }; - -use gimli::{ - DebuggingInformationEntry, FileEntry, LineProgramHeader, Location, UnitOffset, UnwindContext, +pub use variable::{ + Variable, VariableCache, VariableName, VariableNodeType, VariableValue, VariantRole, }; -use object::read::{Object, ObjectSection}; - -use self::variable::VariableValue; /// An error occurred while debugging the target. #[derive(Debug, thiserror::Error)] @@ -1013,7 +1011,7 @@ impl DebugInfo { } } } - VariableNodeType::DoNotRecurse | VariableNodeType::RecurseToBaseType => { + _ => { // Do nothing. These have already been recursed to their maximum. } } diff --git a/probe-rs/src/debug/variable.rs b/probe-rs/src/debug/variable.rs index d43a689970..12342ec8a9 100644 --- a/probe-rs/src/debug/variable.rs +++ b/probe-rs/src/debug/variable.rs @@ -383,6 +383,12 @@ pub enum VariableNodeType { /// - Rule: For now, Array types WILL ALWAYS BE recursed. TODO: Evaluate if it is beneficial to defer these. /// - Rule: For now, Union types WILL ALWAYS BE recursed. TODO: Evaluate if it is beneficial to defer these. RecurseToBaseType, + /// SVD Device Peripherals + SvdPeripheral, + /// SVD Peripheral Registers + SvdRegister, + /// SVD Register Fields + SvdField, } impl VariableNodeType { @@ -567,7 +573,7 @@ impl Variable { /// Implementing set_value(), because the library passes errors into the value of the variable. /// This ensures debug front ends can see the errors, but doesn't fail because of a single variable not being able to decode correctly. - pub(crate) fn set_value(&mut self, new_value: VariableValue) { + pub fn set_value(&mut self, new_value: VariableValue) { // Allow some block when logic requires it. #[allow(clippy::if_same_then_else)] if new_value.is_valid() { @@ -667,7 +673,42 @@ impl Variable { pub fn get_value(&self, variable_cache: &VariableCache) -> String { // Allow for chained `if let` without complaining #[allow(clippy::if_same_then_else)] - if !self.value.is_empty() { + if VariableNodeType::SvdRegister == self.variable_node_type { + if let VariableValue::Valid(register_value) = &self.value { + if let Ok(register_u32_value) = register_value.parse::() { + format!( + "{:#032b} @ {:#010X}", + register_u32_value, self.memory_location + ) + } else { + format!("Invalid register value {}", register_value) + } + } else { + format!("{}", self.value) + } + } else if VariableNodeType::SvdField == self.variable_node_type { + // In this special case, we extract just the bits we need from the stored value of the register. + if let VariableValue::Valid(register_value) = &self.value { + if let Ok(register_u32_value) = register_value.parse::() { + let mut bit_value: u32 = register_u32_value << self.range_lower_bound; + bit_value >>= 32 - (self.range_upper_bound - self.range_lower_bound); + format!( + "{:#b} @ {:#010X}:{}..{}", + bit_value, + self.memory_location, + self.range_lower_bound, + self.range_upper_bound + ) + } else { + format!( + "Invalid bit range {}..{} from value {}", + self.range_lower_bound, self.range_upper_bound, register_value + ) + } + } else { + format!("{}", self.value) + } + } else if !self.value.is_empty() { // The `value` for this `Variable` is non empty because ... // - It is base data type for which a value was determined based on the core runtime, or ... // - We encountered an error somewhere, so report it to the user @@ -713,15 +754,37 @@ impl Variable { } /// Evaluate the variable's result if possible and set self.value, or else set self.value as the error String. - fn extract_value(&mut self, core: &mut Core<'_>, variable_cache: &VariableCache) { - // Quick exit if we don't really need to do much more. - if !self.value.is_empty() - // The value was set explicitly, so just leave it as is., or it was an error, so don't attempt anything else - || self.memory_location == variable::VariableLocation::Value - || !self.memory_location.valid() + pub fn extract_value(&mut self, core: &mut Core<'_>, variable_cache: &VariableCache) { + // Special handling for SVD registers + if self.variable_node_type == VariableNodeType::SvdRegister { + match core.read_word_32(self.memory_location as u32) { + Ok(u32_value) => self.value = VariableValue::Valid(u32_value.to_string()), + Err(error) => { + self.value = VariableValue::Error(format!( + "Unable to read peripheral register value @ {:#010X} : {:?}", + self.memory_location, error + )) + } + } + return; + } else if self.variable_node_type == VariableNodeType::SvdField { + if let Some(parent_variable) = + variable_cache.get_variable_by_key(self.parent_key.unwrap_or(0)) + { + self.value = parent_variable.value; + } else { + self.value = VariableValue::Error( + "Unable to retrieve the register value for this field".to_string(), + ); + } + return; + } else if !self.value.is_empty() + // The value was set explicitly, so just leave it as is, or it was an error, so don't attempt anything else + || self.memory_location == u64::MAX // Early on in the process of `Variable` evaluation || self.type_name == VariableType::Unknown { + // Quick exit if we don't really need to do much more. return; } else if self.variable_node_type.is_deferred() { // And we have not previously assigned the value, then assign the type and address as the value From eddb0e4c0faa2d484741e06d040a27a4f26965e7 Mon Sep 17 00:00:00 2001 From: JackN Date: Thu, 24 Mar 2022 15:33:26 -0400 Subject: [PATCH 09/29] Add SVD Peripherals to DAP evaluate request --- debugger/src/debug_adapter/dap_adapter.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/debugger/src/debug_adapter/dap_adapter.rs b/debugger/src/debug_adapter/dap_adapter.rs index 998511c464..6b8f1b51a3 100644 --- a/debugger/src/debug_adapter/dap_adapter.rs +++ b/debugger/src/debug_adapter/dap_adapter.rs @@ -326,6 +326,11 @@ impl DebugAdapter

{ for stack_frame_variable_cache in [ stack_frame.local_variables.as_mut(), stack_frame.static_variables.as_mut(), + target_core + .core_data + .core_peripherals + .as_mut() + .map(|core_peripherals| &mut core_peripherals.svd_variable_cache), ] { if let Some(search_cache) = stack_frame_variable_cache { variable = search_cache From 47ef97bbb497dbd28f4843d13aad66d3d7ed2f67 Mon Sep 17 00:00:00 2001 From: JackN Date: Fri, 25 Mar 2022 09:59:27 -0400 Subject: [PATCH 10/29] Control bit width of SVD Field formatting --- debugger/src/debug_adapter/dap_adapter.rs | 22 ++++++++++++++++------ probe-rs/src/debug/variable.rs | 23 ++++++++--------------- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/debugger/src/debug_adapter/dap_adapter.rs b/debugger/src/debug_adapter/dap_adapter.rs index 6b8f1b51a3..8674538980 100644 --- a/debugger/src/debug_adapter/dap_adapter.rs +++ b/debugger/src/debug_adapter/dap_adapter.rs @@ -287,7 +287,7 @@ impl DebugAdapter

{ variables_reference: 0_i64, }; - // The Variables request always returns a 'evaluate_name' = 'name', this means that the expression will always be the variable name we are looking for. + // The Variables request sometimes returns the variable name, and other times the variable id, so this expression will be tested to determine if it is an id or not. let expression = arguments.expression.clone(); // Make sure we have a valid StackFrame @@ -333,8 +333,12 @@ impl DebugAdapter

{ .map(|core_peripherals| &mut core_peripherals.svd_variable_cache), ] { if let Some(search_cache) = stack_frame_variable_cache { - variable = search_cache - .get_variable_by_name(&VariableName::Named(expression.clone())); + if let Ok(expression_as_key) = expression.parse::() { + variable = search_cache.get_variable_by_key(expression_as_key); + } else { + variable = search_cache + .get_variable_by_name(&VariableName::Named(expression.clone())); + } if variable.is_some() { variable_cache = Some(search_cache); break; @@ -1468,7 +1472,10 @@ impl DebugAdapter

{ ); Variable { name: variable.name.to_string(), - evaluate_name: Some(variable.name.to_string()), + // evaluate_name: Some(variable.name.to_string()), + // Do NOT use evaluate_name. It is impossible to distinguish between duplicate variable + // TODO: Implement qualified names. + evaluate_name: None, memory_reference: Some(format!("{:#010x}", variable.memory_location)), indexed_variables: Some(indexed_child_variables_cnt), named_variables: Some(named_child_variables_cnt), @@ -1615,8 +1622,11 @@ impl DebugAdapter

{ Variable { name: variable.name.to_string(), - evaluate_name: Some(variable.name.to_string()), - memory_reference, + // evaluate_name: Some(variable.name.to_string()), + // Do NOT use evaluate_name. It is impossible to distinguish between duplicate variable + // TODO: Implement qualified names. + evaluate_name: None, + memory_reference: Some(format!("{:#010x}", variable.memory_location)), indexed_variables: Some(indexed_child_variables_cnt), named_variables: Some(named_child_variables_cnt), presentation_hint: None, diff --git a/probe-rs/src/debug/variable.rs b/probe-rs/src/debug/variable.rs index 12342ec8a9..2f248c8a70 100644 --- a/probe-rs/src/debug/variable.rs +++ b/probe-rs/src/debug/variable.rs @@ -693,11 +693,12 @@ impl Variable { let mut bit_value: u32 = register_u32_value << self.range_lower_bound; bit_value >>= 32 - (self.range_upper_bound - self.range_lower_bound); format!( - "{:#b} @ {:#010X}:{}..{}", + "{:#0width$b} @ {:#010X}:{}..{}", bit_value, self.memory_location, self.range_lower_bound, - self.range_upper_bound + self.range_upper_bound, + width = (self.range_upper_bound - self.range_lower_bound + 2) as usize ) } else { format!( @@ -755,8 +756,11 @@ impl Variable { /// Evaluate the variable's result if possible and set self.value, or else set self.value as the error String. pub fn extract_value(&mut self, core: &mut Core<'_>, variable_cache: &VariableCache) { - // Special handling for SVD registers - if self.variable_node_type == VariableNodeType::SvdRegister { + // Special handling for SVD registers. + // Because we cache the SVD structure once per sesion, we have to re-read the actual register values whenever queried. + if self.variable_node_type == VariableNodeType::SvdRegister + || self.variable_node_type == VariableNodeType::SvdField + { match core.read_word_32(self.memory_location as u32) { Ok(u32_value) => self.value = VariableValue::Valid(u32_value.to_string()), Err(error) => { @@ -767,17 +771,6 @@ impl Variable { } } return; - } else if self.variable_node_type == VariableNodeType::SvdField { - if let Some(parent_variable) = - variable_cache.get_variable_by_key(self.parent_key.unwrap_or(0)) - { - self.value = parent_variable.value; - } else { - self.value = VariableValue::Error( - "Unable to retrieve the register value for this field".to_string(), - ); - } - return; } else if !self.value.is_empty() // The value was set explicitly, so just leave it as is, or it was an error, so don't attempt anything else || self.memory_location == u64::MAX From efca8b912d425298915fd841c476b64bb8126a0b Mon Sep 17 00:00:00 2001 From: JackN Date: Fri, 25 Mar 2022 12:07:29 -0400 Subject: [PATCH 11/29] SVD: Respect Register read impacts and access --- debugger/src/debug_adapter/dap_adapter.rs | 2 +- debugger/src/debugger/debug_rtt.rs | 8 +- debugger/src/debugger/session_data.rs | 22 ++- debugger/src/peripherals/svd_variables.rs | 162 +++++++++++++++++----- probe-rs/src/debug/variable.rs | 10 +- 5 files changed, 145 insertions(+), 59 deletions(-) diff --git a/debugger/src/debug_adapter/dap_adapter.rs b/debugger/src/debug_adapter/dap_adapter.rs index 8674538980..6b7032013a 100644 --- a/debugger/src/debug_adapter/dap_adapter.rs +++ b/debugger/src/debug_adapter/dap_adapter.rs @@ -1458,7 +1458,7 @@ impl DebugAdapter

{ { let dap_variables: Vec = core_peripherals .svd_variable_cache - .get_children(Some(arguments.variables_reference))? + .get_children(Some(search_variable.variable_key))? .iter_mut() // Convert the `probe_rs::debug::Variable` to `probe_rs_debugger::dap_types::Variable` .map(|variable| { diff --git a/debugger/src/debugger/debug_rtt.rs b/debugger/src/debugger/debug_rtt.rs index 82f3084220..f9455f5c44 100644 --- a/debugger/src/debugger/debug_rtt.rs +++ b/debugger/src/debugger/debug_rtt.rs @@ -20,12 +20,8 @@ impl RttConnection { ) -> bool { let mut at_least_one_channel_had_data = false; for debugger_rtt_channel in self.debugger_rtt_channels.iter_mut() { - at_least_one_channel_had_data = at_least_one_channel_had_data - | debugger_rtt_channel.poll_rtt_data( - target_core, - debug_adapter, - &mut self.target_rtt, - ) + at_least_one_channel_had_data |= + debugger_rtt_channel.poll_rtt_data(target_core, debug_adapter, &mut self.target_rtt) } at_least_one_channel_had_data } diff --git a/debugger/src/debugger/session_data.rs b/debugger/src/debugger/session_data.rs index 32c46f3581..6176735a83 100644 --- a/debugger/src/debugger/session_data.rs +++ b/debugger/src/debugger/session_data.rs @@ -4,7 +4,6 @@ use super::{ }; use crate::{ debug_adapter::{dap_adapter::DebugAdapter, protocol::ProtocolAdapter}, - peripherals::svd_variables::SvdCache, DebuggerError, }; use anyhow::{anyhow, Result}; @@ -160,15 +159,14 @@ impl SessionData { .core_configs .iter() .filter(|&core_config| { - if let Some(_) = target_session - .list_cores() - .iter() - .find(|(target_core_index, _)| *target_core_index == core_config.core_index) - { - true - } else { - false - } + matches!( + target_session + .list_cores() + .iter() + .find(|(target_core_index, _)| *target_core_index + == core_config.core_index), + Some(_) + ) }) .cloned() .collect::>(); @@ -244,8 +242,8 @@ impl SessionData { if let Ok(mut target_core) = self.attach_core(core_config.core_index) { if let Some(core_rtt) = &mut target_core.core_data.rtt_connection { // We should poll the target for rtt data. - at_least_one_channel_had_data = at_least_one_channel_had_data - | core_rtt.process_rtt_data(debug_adapter, &mut target_core.core); + at_least_one_channel_had_data |= + core_rtt.process_rtt_data(debug_adapter, &mut target_core.core); } else { // We have not yet reached the point in the target application where the RTT buffers are initialized, so let's check again. if debug_adapter.last_known_status != CoreStatus::Unknown diff --git a/debugger/src/peripherals/svd_variables.rs b/debugger/src/peripherals/svd_variables.rs index d60d49c535..4b151e131f 100644 --- a/debugger/src/peripherals/svd_variables.rs +++ b/debugger/src/peripherals/svd_variables.rs @@ -1,11 +1,11 @@ use crate::DebuggerError; use probe_rs::{ - debug::{DebugError, Variable, VariableCache, VariableName}, + debug::{Variable, VariableCache, VariableName}, Core, }; -use std::{any, fmt::Debug, fs::File, io::Read, path::PathBuf}; +use std::{fmt::Debug, fs::File, io::Read, path::Path}; use svd_parser::{self as svd, ValidateLevel}; -use svd_rs::{peripheral, Device, EnumeratedValues, FieldInfo, PeripheralInfo, RegisterInfo}; +use svd_rs::{Access, Device, EnumeratedValues, FieldInfo, PeripheralInfo, RegisterInfo}; /// The SVD file contents and related data #[derive(Debug)] @@ -18,12 +18,12 @@ pub(crate) struct SvdCache { impl SvdCache { /// Create the SVD cache for a specific core. This function loads the file, parses it, and then builds the VariableCache. - pub(crate) fn new(svd_file: &PathBuf, core: &mut Core) -> Result { + pub(crate) fn new(svd_file: &Path, core: &mut Core) -> Result { let svd_xml = &mut String::new(); - match File::open(svd_file.as_path()) { + match File::open(svd_file) { Ok(mut svd_opened_file) => { - svd_opened_file.read_to_string(svd_xml); - match svd::parse(&svd_xml) { + let _ = svd_opened_file.read_to_string(svd_xml); + match svd::parse(svd_xml) { Ok(peripheral_device) => Ok(SvdCache { svd_variable_cache: variable_cache_from_svd(peripheral_device, core)?, }), @@ -71,7 +71,6 @@ pub(crate) fn variable_cache_from_svd( core, )?; for register in &resolve_registers(peripheral)? { - // TODO: Implement warnings for users if they are going to read registers that have a side effect on reading. let mut register_variable = Variable::new(None, None); register_variable.name = VariableName::Named(register.name.clone()); register_variable.type_name = register @@ -81,6 +80,19 @@ pub(crate) fn variable_cache_from_svd( register_variable.variable_node_type = probe_rs::debug::VariableNodeType::SvdRegister; register_variable.memory_location = peripheral.base_address + register.address_offset as u64; + let mut register_has_restricted_read = false; + if register.read_action.is_some() + || (if let Some(register_access) = register.properties.access { + register_access == Access::ReadWriteOnce || register_access == Access::WriteOnly + } else { + false + }) + { + register_variable.set_value(probe_rs::debug::VariableValue::Error( + "Register access doesn't allow reading, or will have side effects.".to_string(), + )); + register_has_restricted_read = true; + } register_variable = svd_cache.cache_variable( Some(peripheral_variable.variable_key), register_variable, @@ -98,8 +110,36 @@ pub(crate) fn variable_cache_from_svd( // For SVD fields, we overload the range_lower_bound and range_upper_bound as the bit range LSB and MSB. field_variable.range_lower_bound = field.bit_offset() as i64; field_variable.range_upper_bound = (field.bit_offset() + field.bit_width()) as i64; + if register_has_restricted_read + || field.read_action.is_some() + || (if let Some(field_access) = field.access { + field_access == Access::ReadWriteOnce || field_access == Access::WriteOnly + } else { + false + }) + { + register_variable.set_value(probe_rs::debug::VariableValue::Error( + "Field or Register access doesn't allow reading, or will have side effects." + .to_string(), + )); + register_has_restricted_read = true; + if !register_has_restricted_read { + // If we can't read any of the bits, then don't read the register either. + register_variable.set_value(probe_rs::debug::VariableValue::Error( + "Register access doesn't allow reading, or will have side effects." + .to_string(), + )); + register_has_restricted_read = true; + register_variable = svd_cache.cache_variable( + Some(peripheral_variable.variable_key), + register_variable, + core, + )?; + } + } + // TODO: Extend the Variable definition, so that we can resolve the EnumeratedValues for fields. - field_variable = svd_cache.cache_variable( + svd_cache.cache_variable( Some(register_variable.variable_key), field_variable, core, @@ -214,6 +254,8 @@ pub(crate) fn resolve_registers( for peripheral_register in peripheral.registers() { // TODO: Need to code for the impact of MaybeArray results. let mut register_builder = RegisterInfo::builder(); + // Deriving the properties starts from the peripheral level defaults. + let mut register_properties = peripheral.default_register_properties; if let Some(derived_from) = &peripheral_register.derived_from { if let Some(template_register) = peripheral.get_register(derived_from) { if template_register.display_name.is_some() { @@ -226,19 +268,39 @@ pub(crate) fn resolve_registers( } if template_register.modified_write_values.is_some() { register_builder = register_builder - .modified_write_values(template_register.modified_write_values.clone()); + .modified_write_values(template_register.modified_write_values); } if template_register.write_constraint.is_some() { - register_builder = register_builder - .write_constraint(template_register.write_constraint.clone()); + register_builder = + register_builder.write_constraint(template_register.write_constraint); } if template_register.read_action.is_some() { - register_builder = - register_builder.read_action(template_register.read_action.clone()); + register_builder = register_builder.read_action(template_register.read_action); } if template_register.fields.is_some() { register_builder = register_builder.fields(template_register.fields.clone()); } + // We don't update the register_builder properties directly until the next step. + if template_register.properties.size.is_some() { + register_properties = + register_properties.size(template_register.properties.size); + } + if template_register.properties.access.is_some() { + register_properties = + register_properties.access(template_register.properties.access); + } + if template_register.properties.protection.is_some() { + register_properties = + register_properties.protection(template_register.properties.protection); + } + if template_register.properties.reset_value.is_some() { + register_properties = + register_properties.reset_value(template_register.properties.reset_value); + } + if template_register.properties.reset_mask.is_some() { + register_properties = + register_properties.reset_mask(template_register.properties.reset_mask); + } } else { return Err(DebuggerError::Other(anyhow::anyhow!( "Unable to retrieve 'derived_from' SVD register: {:?}", @@ -247,7 +309,14 @@ pub(crate) fn resolve_registers( }; } // Irrespective of derived_from values, set the values we need. - register_builder = register_builder.name(peripheral_register.name.clone()); + let mut register_name = peripheral_register.name.clone(); + if let Some(prefix) = &peripheral.prepend_to_name { + register_name = format!("{}{}", prefix, register_name); + } + if let Some(suffix) = &peripheral.append_to_name { + register_name = format!("{}{}", register_name, suffix); + } + register_builder = register_builder.name(register_name); if peripheral_register.display_name.is_some() { register_builder = register_builder.display_name(peripheral_register.display_name.clone()); @@ -256,24 +325,43 @@ pub(crate) fn resolve_registers( register_builder = register_builder.description(peripheral_register.description.clone()); } - register_builder = - register_builder.address_offset(peripheral_register.address_offset.clone()); - register_builder = register_builder.properties(peripheral_register.properties.clone()); + register_builder = register_builder.address_offset(peripheral_register.address_offset); + register_builder = register_builder.properties(peripheral_register.properties); if peripheral_register.modified_write_values.is_some() { - register_builder = register_builder - .modified_write_values(peripheral_register.modified_write_values.clone()); + register_builder = + register_builder.modified_write_values(peripheral_register.modified_write_values); } if peripheral_register.write_constraint.is_some() { register_builder = - register_builder.write_constraint(peripheral_register.write_constraint.clone()); + register_builder.write_constraint(peripheral_register.write_constraint); } if peripheral_register.read_action.is_some() { - register_builder = - register_builder.read_action(peripheral_register.read_action.clone()); + register_builder = register_builder.read_action(peripheral_register.read_action); } if peripheral_register.fields.is_some() { register_builder = register_builder.fields(peripheral_register.fields.clone()); } + // Complete the derive of the register properties. + if peripheral_register.properties.size.is_some() { + register_properties = register_properties.size(peripheral_register.properties.size); + } + if peripheral_register.properties.access.is_some() { + register_properties = register_properties.access(peripheral_register.properties.access); + } + if peripheral_register.properties.protection.is_some() { + register_properties = + register_properties.protection(peripheral_register.properties.protection); + } + if peripheral_register.properties.reset_value.is_some() { + register_properties = + register_properties.reset_value(peripheral_register.properties.reset_value); + } + if peripheral_register.properties.reset_mask.is_some() { + register_properties = + register_properties.reset_mask(peripheral_register.properties.reset_mask); + } + register_builder = register_builder.properties(register_properties); + // Not that the register_builder has been updated, we can build it. let resolved_register = register_builder .build(ValidateLevel::Weak) .map_err(|error| DebuggerError::Other(anyhow::anyhow!("{:?}", error)))?; @@ -295,18 +383,17 @@ pub(crate) fn resolve_fields(register: &RegisterInfo) -> Result, field_builder = field_builder.description(template_field.description.clone()); } if template_field.access.is_some() { - field_builder = field_builder.access(template_field.access.clone()); + field_builder = field_builder.access(template_field.access); } if template_field.modified_write_values.is_some() { - field_builder = field_builder - .modified_write_values(template_field.modified_write_values.clone()); + field_builder = + field_builder.modified_write_values(template_field.modified_write_values); } if template_field.write_constraint.is_some() { - field_builder = - field_builder.write_constraint(template_field.write_constraint.clone()); + field_builder = field_builder.write_constraint(template_field.write_constraint); } if template_field.read_action.is_some() { - field_builder = field_builder.read_action(template_field.read_action.clone()); + field_builder = field_builder.read_action(template_field.read_action); } } else { return Err(DebuggerError::Other(anyhow::anyhow!( @@ -320,17 +407,17 @@ pub(crate) fn resolve_fields(register: &RegisterInfo) -> Result, if register_field.description.is_some() { field_builder = field_builder.description(register_field.description.clone()); } - field_builder = field_builder.bit_range(register_field.bit_range.clone()); - field_builder = field_builder.access(register_field.access.clone()); + field_builder = field_builder.bit_range(register_field.bit_range); + field_builder = field_builder.access(register_field.access); if register_field.modified_write_values.is_some() { field_builder = - field_builder.modified_write_values(register_field.modified_write_values.clone()); + field_builder.modified_write_values(register_field.modified_write_values); } if register_field.write_constraint.is_some() { - field_builder = field_builder.write_constraint(register_field.write_constraint.clone()); + field_builder = field_builder.write_constraint(register_field.write_constraint); } if register_field.read_action.is_some() { - field_builder = field_builder.read_action(register_field.read_action.clone()); + field_builder = field_builder.read_action(register_field.read_action); } field_builder = field_builder.enumerated_values(register_field.enumerated_values.clone()); let resolved_field = field_builder @@ -341,6 +428,8 @@ pub(crate) fn resolve_fields(register: &RegisterInfo) -> Result, Ok(resolved_fields) } +// TODO: Implement using these enumerated values for SVD fields. +#[allow(dead_code)] /// Resolve all the enumerated values of a field through their (optional) `derived_from` values. pub(crate) fn enumerated_values(field: FieldInfo) -> Result, DebuggerError> { // TODO: Need to code for the impact of enumerated value clusters. @@ -359,8 +448,7 @@ pub(crate) fn enumerated_values(field: FieldInfo) -> Result Result bool { match self { VariableNodeType::ReferenceOffset(_) @@ -756,11 +757,14 @@ impl Variable { /// Evaluate the variable's result if possible and set self.value, or else set self.value as the error String. pub fn extract_value(&mut self, core: &mut Core<'_>, variable_cache: &VariableCache) { - // Special handling for SVD registers. - // Because we cache the SVD structure once per sesion, we have to re-read the actual register values whenever queried. - if self.variable_node_type == VariableNodeType::SvdRegister + if let VariableValue::Error(_) = self.value { + // Nothing more to do ... + return; + } else if self.variable_node_type == VariableNodeType::SvdRegister || self.variable_node_type == VariableNodeType::SvdField { + // Special handling for SVD registers. + // Because we cache the SVD structure once per sesion, we have to re-read the actual register values whenever queried. match core.read_word_32(self.memory_location as u32) { Ok(u32_value) => self.value = VariableValue::Valid(u32_value.to_string()), Err(error) => { From cb6aa904b24c1560c0c830b552dbe5b62802c161 Mon Sep 17 00:00:00 2001 From: JackN Date: Fri, 25 Mar 2022 14:19:49 -0400 Subject: [PATCH 12/29] SVD: Fix field values --- debugger/src/peripherals/svd_variables.rs | 1 - probe-rs/src/debug/variable.rs | 7 ++++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/debugger/src/peripherals/svd_variables.rs b/debugger/src/peripherals/svd_variables.rs index 4b151e131f..9c35a85dbb 100644 --- a/debugger/src/peripherals/svd_variables.rs +++ b/debugger/src/peripherals/svd_variables.rs @@ -204,7 +204,6 @@ pub(crate) fn resolve_peripherals( } // Irrespective of derived_from values, set the values we need. peripheral_builder = peripheral_builder.name(device_peripheral.name.clone()); - peripheral_builder = peripheral_builder.description(device_peripheral.description.clone()); if device_peripheral.description.is_some() { peripheral_builder = peripheral_builder.description(device_peripheral.description.clone()); diff --git a/probe-rs/src/debug/variable.rs b/probe-rs/src/debug/variable.rs index d5c053efff..bfefd789d0 100644 --- a/probe-rs/src/debug/variable.rs +++ b/probe-rs/src/debug/variable.rs @@ -678,7 +678,7 @@ impl Variable { if let VariableValue::Valid(register_value) = &self.value { if let Ok(register_u32_value) = register_value.parse::() { format!( - "{:#032b} @ {:#010X}", + "{:#034b} @ {:#010X}", register_u32_value, self.memory_location ) } else { @@ -691,7 +691,8 @@ impl Variable { // In this special case, we extract just the bits we need from the stored value of the register. if let VariableValue::Valid(register_value) = &self.value { if let Ok(register_u32_value) = register_value.parse::() { - let mut bit_value: u32 = register_u32_value << self.range_lower_bound; + let mut bit_value: u32 = register_u32_value.reverse_bits(); + bit_value <<= self.range_lower_bound; bit_value >>= 32 - (self.range_upper_bound - self.range_lower_bound); format!( "{:#0width$b} @ {:#010X}:{}..{}", @@ -766,7 +767,7 @@ impl Variable { // Special handling for SVD registers. // Because we cache the SVD structure once per sesion, we have to re-read the actual register values whenever queried. match core.read_word_32(self.memory_location as u32) { - Ok(u32_value) => self.value = VariableValue::Valid(u32_value.to_string()), + Ok(u32_value) => self.value = VariableValue::Valid(u32_value.to_le().to_string()), Err(error) => { self.value = VariableValue::Error(format!( "Unable to read peripheral register value @ {:#010X} : {:?}", From 910a57381cec4565b6428a0b8b8e57ac162786c8 Mon Sep 17 00:00:00 2001 From: JackN Date: Sat, 26 Mar 2022 06:49:01 -0400 Subject: [PATCH 13/29] SVD Qualify Peripheral.Register.Field in names --- debugger/src/debug_adapter/dap_adapter.rs | 30 ++++++++++----- debugger/src/peripherals/svd_variables.rs | 46 +++++++++++++---------- probe-rs/src/debug/variable.rs | 18 ++++++--- 3 files changed, 60 insertions(+), 34 deletions(-) diff --git a/debugger/src/debug_adapter/dap_adapter.rs b/debugger/src/debug_adapter/dap_adapter.rs index 6b7032013a..eb4bdbb76b 100644 --- a/debugger/src/debug_adapter/dap_adapter.rs +++ b/debugger/src/debug_adapter/dap_adapter.rs @@ -8,7 +8,7 @@ use dap_types::*; use num_traits::Zero; use parse_int::parse; use probe_rs::{ - debug::{ColumnType, Registers, SourceLocation, VariableCache, VariableName}, + debug::{ColumnType, Registers, SourceLocation, VariableCache, VariableName, VariableNodeType}, CoreStatus, HaltReason, MemoryInterface, }; use probe_rs_cli_util::rtt; @@ -323,7 +323,7 @@ impl DebugAdapter

{ let mut variable_cache: Option<&mut VariableCache> = None; // Search through available caches and stop as soon as the variable is found #[allow(clippy::manual_flatten)] - for stack_frame_variable_cache in [ + for variable_cache_entry in [ stack_frame.local_variables.as_mut(), stack_frame.static_variables.as_mut(), target_core @@ -332,14 +332,19 @@ impl DebugAdapter

{ .as_mut() .map(|core_peripherals| &mut core_peripherals.svd_variable_cache), ] { - if let Some(search_cache) = stack_frame_variable_cache { + if let Some(search_cache) = variable_cache_entry { if let Ok(expression_as_key) = expression.parse::() { variable = search_cache.get_variable_by_key(expression_as_key); } else { variable = search_cache .get_variable_by_name(&VariableName::Named(expression.clone())); } - if variable.is_some() { + if let Some(variable) = &mut variable { + if variable.variable_node_type == VariableNodeType::SvdRegister + || variable.variable_node_type == VariableNodeType::SvdField + { + variable.extract_value(&mut target_core.core, search_cache) + } variable_cache = Some(search_cache); break; } @@ -1471,11 +1476,18 @@ impl DebugAdapter

{ &mut core_peripherals.svd_variable_cache, ); Variable { - name: variable.name.to_string(), - // evaluate_name: Some(variable.name.to_string()), - // Do NOT use evaluate_name. It is impossible to distinguish between duplicate variable - // TODO: Implement qualified names. - evaluate_name: None, + name: if let VariableName::Named(variable_name) = &variable.name { + if let Some(last_part) = variable_name.split_terminator('.').last() + { + last_part.to_string() + } else { + variable_name.to_string() + } + } else { + variable.name.to_string() + }, + // We use fully qualified Peripheral.Register.Field form to ensure the `evaluate` request can find the right registers and fields by name. + evaluate_name: Some(variable.name.to_string()), memory_reference: Some(format!("{:#010x}", variable.memory_location)), indexed_variables: Some(indexed_child_variables_cnt), named_variables: Some(named_child_variables_cnt), diff --git a/debugger/src/peripherals/svd_variables.rs b/debugger/src/peripherals/svd_variables.rs index 9c35a85dbb..217a1cb2ee 100644 --- a/debugger/src/peripherals/svd_variables.rs +++ b/debugger/src/peripherals/svd_variables.rs @@ -72,7 +72,11 @@ pub(crate) fn variable_cache_from_svd( )?; for register in &resolve_registers(peripheral)? { let mut register_variable = Variable::new(None, None); - register_variable.name = VariableName::Named(register.name.clone()); + register_variable.name = VariableName::Named(format!( + "{}.{}", + &peripheral_variable.name, + register.name.clone() + )); register_variable.type_name = register .description .clone() @@ -100,7 +104,11 @@ pub(crate) fn variable_cache_from_svd( )?; for field in &resolve_fields(register)? { let mut field_variable = Variable::new(None, None); - field_variable.name = VariableName::Named(field.name.clone()); + field_variable.name = VariableName::Named(format!( + "{}.{}", + ®ister_variable.name, + field.name.clone() + )); field_variable.type_name = field .description .clone() @@ -110,34 +118,34 @@ pub(crate) fn variable_cache_from_svd( // For SVD fields, we overload the range_lower_bound and range_upper_bound as the bit range LSB and MSB. field_variable.range_lower_bound = field.bit_offset() as i64; field_variable.range_upper_bound = (field.bit_offset() + field.bit_width()) as i64; - if register_has_restricted_read - || field.read_action.is_some() + if register_has_restricted_read { + register_variable.set_value(probe_rs::debug::VariableValue::Error( + "Register access doesn't allow reading, or will have side effects." + .to_string(), + )); + } else if field.read_action.is_some() || (if let Some(field_access) = field.access { field_access == Access::ReadWriteOnce || field_access == Access::WriteOnly } else { false }) { + field_variable.set_value(probe_rs::debug::VariableValue::Error( + "Field access doesn't allow reading, or will have side effects." + .to_string(), + )); + // If we can't read any of the bits, then don't read the register either. register_variable.set_value(probe_rs::debug::VariableValue::Error( - "Field or Register access doesn't allow reading, or will have side effects." + "Some fields' access doesn't allow reading, or will have side effects." .to_string(), )); register_has_restricted_read = true; - if !register_has_restricted_read { - // If we can't read any of the bits, then don't read the register either. - register_variable.set_value(probe_rs::debug::VariableValue::Error( - "Register access doesn't allow reading, or will have side effects." - .to_string(), - )); - register_has_restricted_read = true; - register_variable = svd_cache.cache_variable( - Some(peripheral_variable.variable_key), - register_variable, - core, - )?; - } + register_variable = svd_cache.cache_variable( + Some(peripheral_variable.variable_key), + register_variable, + core, + )?; } - // TODO: Extend the Variable definition, so that we can resolve the EnumeratedValues for fields. svd_cache.cache_variable( Some(register_variable.variable_key), diff --git a/probe-rs/src/debug/variable.rs b/probe-rs/src/debug/variable.rs index bfefd789d0..fdec990562 100644 --- a/probe-rs/src/debug/variable.rs +++ b/probe-rs/src/debug/variable.rs @@ -99,7 +99,13 @@ impl VariableCache { // As the final act, we need to update the variable with an appropriate value. // This requires distinct steps to ensure we don't get `borrow` conflicts on the variable cache. if let Some(mut stored_variable) = self.get_variable_by_key(stored_key) { - stored_variable.extract_value(core, self); + if !(stored_variable.variable_node_type == VariableNodeType::SvdPeripheral + || stored_variable.variable_node_type == VariableNodeType::SvdRegister + || stored_variable.variable_node_type == VariableNodeType::SvdField) + { + // Only do this for non-SVD variables. Those will extract their value everytime they are read from the client. + stored_variable.extract_value(core, self); + } if self .variable_hash_map .insert(stored_variable.variable_key, stored_variable.clone()) @@ -678,7 +684,7 @@ impl Variable { if let VariableValue::Valid(register_value) = &self.value { if let Ok(register_u32_value) = register_value.parse::() { format!( - "{:#034b} @ {:#010X}", + "{:032b} @ {:#010X}", register_u32_value, self.memory_location ) } else { @@ -691,16 +697,16 @@ impl Variable { // In this special case, we extract just the bits we need from the stored value of the register. if let VariableValue::Valid(register_value) = &self.value { if let Ok(register_u32_value) = register_value.parse::() { - let mut bit_value: u32 = register_u32_value.reverse_bits(); - bit_value <<= self.range_lower_bound; + let mut bit_value: u32 = register_u32_value; + bit_value <<= 32 - self.range_upper_bound; bit_value >>= 32 - (self.range_upper_bound - self.range_lower_bound); format!( - "{:#0width$b} @ {:#010X}:{}..{}", + "{:0width$b} @ {:#010X}:{}..{}", bit_value, self.memory_location, self.range_lower_bound, self.range_upper_bound, - width = (self.range_upper_bound - self.range_lower_bound + 2) as usize + width = (self.range_upper_bound - self.range_lower_bound) as usize ) } else { format!( From 83a2e51bde4bc442e47a94925b968358a046ac43 Mon Sep 17 00:00:00 2001 From: JackN Date: Sat, 26 Mar 2022 08:07:00 -0400 Subject: [PATCH 14/29] SVD: Add Peripheral Group to structure --- debugger/src/peripherals/svd_variables.rs | 48 +++++++++++++++++------ 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/debugger/src/peripherals/svd_variables.rs b/debugger/src/peripherals/svd_variables.rs index 217a1cb2ee..a1da131834 100644 --- a/debugger/src/peripherals/svd_variables.rs +++ b/debugger/src/peripherals/svd_variables.rs @@ -1,6 +1,6 @@ use crate::DebuggerError; use probe_rs::{ - debug::{Variable, VariableCache, VariableName}, + debug::{Variable, VariableCache, VariableName, VariableNodeType}, Core, }; use std::{fmt::Debug, fs::File, io::Read, path::Path}; @@ -49,14 +49,41 @@ pub(crate) fn variable_cache_from_svd( device_root_variable.variable_node_type = probe_rs::debug::VariableNodeType::DoNotRecurse; device_root_variable.name = VariableName::PeripheralScopeRoot; device_root_variable = svd_cache.cache_variable(None, device_root_variable, core)?; + // Adding the Peripheral Group Name as an additional level in the structure helps to keep the 'variable tree' more compact, but more importantly, it helps to avoid having duplicate variable names that conflict with hal crates. + let mut peripheral_group_variable = Variable::new(None, None); + peripheral_group_variable.name = VariableName::Named(peripheral_device.name.clone()); + let mut peripheral_parent_key = device_root_variable.variable_key; for peripheral in &resolve_peripherals(&peripheral_device)? { - // TODO: Create a parent structure for peripheral groups with more than one member. + if let (Some(peripheral_group_name), VariableName::Named(variable_group_name)) = + (&peripheral.group_name, &peripheral_group_variable.name) + { + if variable_group_name != peripheral_group_name { + peripheral_group_variable = Variable::new(None, None); + peripheral_group_variable.name = VariableName::Named(peripheral_group_name.clone()); + peripheral_group_variable.type_name = "Peripheral Group".to_string(); + peripheral_group_variable.variable_node_type = VariableNodeType::SvdPeripheral; + peripheral_group_variable.set_value(probe_rs::debug::VariableValue::Valid( + peripheral + .description + .clone() + .unwrap_or_else(|| peripheral.name.clone()), + )); + peripheral_group_variable = svd_cache.cache_variable( + Some(device_root_variable.variable_key), + peripheral_group_variable, + core, + )?; + peripheral_parent_key = peripheral_group_variable.variable_key; + } + } + let mut peripheral_variable = Variable::new(None, None); - peripheral_variable.name = VariableName::Named(peripheral.name.clone()); - peripheral_variable.type_name = peripheral - .description - .clone() - .unwrap_or_else(|| "Device Peripheral".to_string()); + peripheral_variable.name = VariableName::Named(format!( + "{}.{}", + peripheral_group_variable.name.clone(), + peripheral.name.clone() + )); + peripheral_variable.type_name = "Peripheral".to_string(); peripheral_variable.variable_node_type = probe_rs::debug::VariableNodeType::SvdPeripheral; peripheral_variable.memory_location = peripheral.base_address; peripheral_variable.set_value(probe_rs::debug::VariableValue::Valid( @@ -65,11 +92,8 @@ pub(crate) fn variable_cache_from_svd( .clone() .unwrap_or_else(|| format!("{}", peripheral_variable.name)), )); - peripheral_variable = svd_cache.cache_variable( - Some(device_root_variable.variable_key), - peripheral_variable, - core, - )?; + peripheral_variable = + svd_cache.cache_variable(Some(peripheral_parent_key), peripheral_variable, core)?; for register in &resolve_registers(peripheral)? { let mut register_variable = Variable::new(None, None); register_variable.name = VariableName::Named(format!( From 2bd92e2b6cea74491941db77c4c30bdf28edcf77 Mon Sep 17 00:00:00 2001 From: JackN Date: Sat, 26 Mar 2022 11:36:47 -0400 Subject: [PATCH 15/29] DAP Client progress indicator for SVD load Add TODO to prevent set variable for SVD Remove TODO tag for progress update in SVD --- debugger/src/debug_adapter/dap_adapter.rs | 6 ++- debugger/src/debugger/debug_entry.rs | 64 ++++++++++++++--------- debugger/src/peripherals/svd_variables.rs | 57 +++++++++++++++++--- 3 files changed, 93 insertions(+), 34 deletions(-) diff --git a/debugger/src/debug_adapter/dap_adapter.rs b/debugger/src/debug_adapter/dap_adapter.rs index eb4bdbb76b..3a671faa2c 100644 --- a/debugger/src/debug_adapter/dap_adapter.rs +++ b/debugger/src/debug_adapter/dap_adapter.rs @@ -401,6 +401,8 @@ impl DebugAdapter

{ let parent_key = arguments.variables_reference; let new_value = arguments.value.clone(); + //TODO: Check for, and prevent SVD Peripheral/Register/Field values from being updated, until such time as we can do it safely. + match target_core .core_data .stack_frames @@ -1978,7 +1980,7 @@ impl DebugAdapter

{ /// The progress has the range [0..1]. pub fn update_progress( &mut self, - progress: f64, + progress: Option, message: Option>, progress_id: i64, ) -> Result { @@ -1991,7 +1993,7 @@ impl DebugAdapter

{ "progressUpdate", Some(ProgressUpdateEventBody { message: message.map(|v| v.into()), - percentage: Some(progress * 100.0), + percentage: progress.map(|progress| progress * 100.0), progress_id: progress_id.to_string(), }), )?; diff --git a/debugger/src/debugger/debug_entry.rs b/debugger/src/debugger/debug_entry.rs index 0c46497a02..070eb0e76a 100644 --- a/debugger/src/debugger/debug_entry.rs +++ b/debugger/src/debugger/debug_entry.rs @@ -546,8 +546,6 @@ impl Debugger { debug_adapter.set_console_log_level( self.config.console_log_level.unwrap_or(ConsoleLog::Error), ); - - debug_adapter.send_response::<()>(launch_attach_request, Ok(None))?; } Err(error) => { let error_message = format!( @@ -610,7 +608,9 @@ impl Debugger { &path_to_elf )); - let progress_id = debug_adapter.start_progress("Flashing device", None).ok(); + let progress_id = debug_adapter + .start_progress("Flashing device", Some(launch_attach_request.seq)) + .ok(); let mut download_options = DownloadOptions::default(); download_options.keep_unwritten_bytes = @@ -666,7 +666,11 @@ impl Debugger { } probe_rs::flashing::ProgressEvent::StartedFilling => { debug_adapter - .update_progress(0.0, Some("Reading Old Pages ..."), id) + .update_progress( + Some(0.0), + Some("Reading Old Pages ..."), + id, + ) .ok(); } probe_rs::flashing::ProgressEvent::PageFilled { @@ -677,7 +681,7 @@ impl Debugger { / flash_progress.total_fill_size as f64; debug_adapter .update_progress( - progress, + Some(progress), Some(format!("Reading Old Pages ({})", progress)), id, ) @@ -686,7 +690,7 @@ impl Debugger { probe_rs::flashing::ProgressEvent::FailedFilling => { debug_adapter .update_progress( - 1.0, + Some(1.0), Some("Reading Old Pages Failed!"), id, ) @@ -695,7 +699,7 @@ impl Debugger { probe_rs::flashing::ProgressEvent::FinishedFilling => { debug_adapter .update_progress( - 1.0, + Some(1.0), Some("Reading Old Pages Complete!"), id, ) @@ -703,7 +707,11 @@ impl Debugger { } probe_rs::flashing::ProgressEvent::StartedErasing => { debug_adapter - .update_progress(0.0, Some("Erasing Sectors ..."), id) + .update_progress( + Some(0.0), + Some("Erasing Sectors ..."), + id, + ) .ok(); } probe_rs::flashing::ProgressEvent::SectorErased { @@ -715,7 +723,7 @@ impl Debugger { / flash_progress.total_sector_size as f64; debug_adapter .update_progress( - progress, + Some(progress), Some(format!("Erasing Sectors ({})", progress)), id, ) @@ -724,7 +732,7 @@ impl Debugger { probe_rs::flashing::ProgressEvent::FailedErasing => { debug_adapter .update_progress( - 1.0, + Some(1.0), Some("Erasing Sectors Failed!"), id, ) @@ -733,7 +741,7 @@ impl Debugger { probe_rs::flashing::ProgressEvent::FinishedErasing => { debug_adapter .update_progress( - 1.0, + Some(1.0), Some("Erasing Sectors Complete!"), id, ) @@ -741,7 +749,11 @@ impl Debugger { } probe_rs::flashing::ProgressEvent::StartedProgramming => { debug_adapter - .update_progress(0.0, Some("Programming Pages ..."), id) + .update_progress( + Some(0.0), + Some("Programming Pages ..."), + id, + ) .ok(); } probe_rs::flashing::ProgressEvent::PageProgrammed { @@ -753,7 +765,7 @@ impl Debugger { / flash_progress.total_page_size as f64; debug_adapter .update_progress( - progress, + Some(progress), Some(format!( "Programming Pages ({:02.0}%)", progress.mul(100_f64) @@ -765,7 +777,7 @@ impl Debugger { probe_rs::flashing::ProgressEvent::FailedProgramming => { debug_adapter .update_progress( - 1.0, + Some(1.0), Some("Flashing Pages Failed!"), id, ) @@ -774,7 +786,7 @@ impl Debugger { probe_rs::flashing::ProgressEvent::FinishedProgramming => { debug_adapter .update_progress( - 1.0, + Some(1.0), Some("Flashing Pages Complete!"), id, ) @@ -839,16 +851,19 @@ impl Debugger { } // Before we complete, load the (optional) CMSIS-SVD file and its variable cache. // Configure the [CorePeripherals]. - // TODO: Implement progress reporting for this operation. if let Some(svd_file) = &target_core_config.svd_file { - target_core.core_data.core_peripherals = - match SvdCache::new(svd_file, &mut target_core.core) { - Ok(core_peripherals) => Some(core_peripherals), - Err(error) => { - log::error!("{:?}", error); - None - } - }; + target_core.core_data.core_peripherals = match SvdCache::new( + svd_file, + &mut target_core.core, + &mut debug_adapter, + launch_attach_request.seq, + ) { + Ok(core_peripherals) => Some(core_peripherals), + Err(error) => { + log::error!("{:?}", error); + None + } + }; } target_core } @@ -869,6 +884,7 @@ impl Debugger { // After flashing and forced setup, we can signal the client that are ready to receive incoming requests. // Send the `initalized` event to client. + debug_adapter.send_response::<()>(launch_attach_request, Ok(None))?; if debug_adapter .send_event::("initialized", None) .is_err() diff --git a/debugger/src/peripherals/svd_variables.rs b/debugger/src/peripherals/svd_variables.rs index a1da131834..dd809c04ae 100644 --- a/debugger/src/peripherals/svd_variables.rs +++ b/debugger/src/peripherals/svd_variables.rs @@ -1,4 +1,7 @@ -use crate::DebuggerError; +use crate::{ + debug_adapter::{dap_adapter::DebugAdapter, protocol::ProtocolAdapter}, + DebuggerError, +}; use probe_rs::{ debug::{Variable, VariableCache, VariableName, VariableNodeType}, Core, @@ -18,21 +21,47 @@ pub(crate) struct SvdCache { impl SvdCache { /// Create the SVD cache for a specific core. This function loads the file, parses it, and then builds the VariableCache. - pub(crate) fn new(svd_file: &Path, core: &mut Core) -> Result { + pub(crate) fn new( + svd_file: &Path, + core: &mut Core, + debug_adapter: &mut DebugAdapter

, + dap_request_id: i64, + ) -> Result { let svd_xml = &mut String::new(); match File::open(svd_file) { Ok(mut svd_opened_file) => { + let progress_id = debug_adapter.start_progress( + format!("Loading SVD file : {:?}", &svd_file).as_str(), + Some(dap_request_id), + )?; let _ = svd_opened_file.read_to_string(svd_xml); - match svd::parse(svd_xml) { - Ok(peripheral_device) => Ok(SvdCache { - svd_variable_cache: variable_cache_from_svd(peripheral_device, core)?, - }), + let svd_cache = match svd::parse(svd_xml) { + Ok(peripheral_device) => { + debug_adapter + .update_progress( + None, + Some(format!("Done loading SVD file :{:?}", &svd_file)), + progress_id, + ) + .ok(); + + Ok(SvdCache { + svd_variable_cache: variable_cache_from_svd( + peripheral_device, + core, + debug_adapter, + progress_id, + )?, + }) + } Err(error) => Err(DebuggerError::Other(anyhow::anyhow!( "Unable to parse CMSIS-SVD file: {:?}. {:?}", svd_file, error, ))), - } + }; + let _ = debug_adapter.end_progress(progress_id)?; + svd_cache } Err(error) => Err(DebuggerError::Other(anyhow::anyhow!("{}", error))), } @@ -40,9 +69,11 @@ impl SvdCache { } /// Create a [`probe_rs::debug::VariableCache`] from a Device that was parsed from a CMSIS-SVD file. -pub(crate) fn variable_cache_from_svd( +pub(crate) fn variable_cache_from_svd( peripheral_device: Device, core: &mut Core, + debug_adapter: &mut DebugAdapter

, + progress_id: i64, ) -> Result { let mut svd_cache = probe_rs::debug::VariableCache::new(); let mut device_root_variable = Variable::new(None, None); @@ -74,6 +105,16 @@ pub(crate) fn variable_cache_from_svd( core, )?; peripheral_parent_key = peripheral_group_variable.variable_key; + debug_adapter + .update_progress( + None, + Some(format!( + "SVD loading peripheral group:{}", + &peripheral_group_name + )), + progress_id, + ) + .ok(); } } From 23924e086eaf932505c1bb77ca8ed7ea9cfe07a6 Mon Sep 17 00:00:00 2001 From: JackN Date: Sun, 27 Mar 2022 11:24:42 -0400 Subject: [PATCH 16/29] CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68dba3e35f..11ebbb6ce7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Debugger: Add support for DAP Requests (ReadMemory, WriteMemory, Evaluate & SetVariable) (#1035) - Debugger: Add support for DAP Requests (Disassemble & SetInstructionBreakpoints) (#1049) - Debugger: Add support for stepping at 'statement' level, plus 'step in', 'step out' (#1056) +- Debugger: Add support for navigating and monitoring SVD Peripheral Registers. () ### Changed @@ -43,6 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated STM32H7 series yaml to support newly released chips. (#1011) - Debugger: Removed the CLI mode, in favour of `probe-rs-cli` which has richer functionality. (#1041) - Renamed `Probe::speed` to `Probe::speed_khz`. +- Debugger: Requires changes to DAP Client `launch.json` to prepare for WIP multi-core support. () ### Fixed From df1a8a9ebab02c8200af741a5fbd69d2e20a8b64 Mon Sep 17 00:00:00 2001 From: JackN Date: Tue, 29 Mar 2022 13:56:02 -0400 Subject: [PATCH 17/29] Fix previously missed error in debug session start --- debugger/src/debugger/debug_entry.rs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/debugger/src/debugger/debug_entry.rs b/debugger/src/debugger/debug_entry.rs index 070eb0e76a..e37bc3d595 100644 --- a/debugger/src/debugger/debug_entry.rs +++ b/debugger/src/debugger/debug_entry.rs @@ -561,18 +561,18 @@ impl Debugger { } }; - let mut session_data = match session_data::SessionData::new(&self.config) { - Ok(session_data) => session_data, + // Validate file specifications in the config. + match self.config.validate_config_files() { + Ok(_) => {} Err(error) => { - debug_adapter.send_error_response(&error)?; return Err(error); } }; - // Validate file specifications in the config. - match self.config.validate_config_files() { - Ok(_) => {} + let mut session_data = match session_data::SessionData::new(&self.config) { + Ok(session_data) => session_data, Err(error) => { + debug_adapter.send_error_response(&error)?; return Err(error); } }; @@ -1007,7 +1007,14 @@ pub fn debug(port: Option, vscode: bool) -> Result<()> { let debug_adapter = DebugAdapter::new(dap_adapter); match debugger.debug_session(debug_adapter) { - Err(_) | Ok(DebuggerStatus::TerminateSession) => { + Err(error) => { + log::error!("probe-rs-debugger session ended: {}", &error); + println!( + "{} CONSOLE: ....Closing session from :{}, due to error: {}", + &program_name, addr, error + ); + } + Ok(DebuggerStatus::TerminateSession) => { println!( "{} CONSOLE: ....Closing session from :{}", &program_name, addr From ed1405312c9f5d774d49457bc173b155f29cc6c9 Mon Sep 17 00:00:00 2001 From: JackN Date: Wed, 30 Mar 2022 06:12:29 -0400 Subject: [PATCH 18/29] Expand error source in dap `send_error_response()` --- debugger/src/debug_adapter/dap_adapter.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/debugger/src/debug_adapter/dap_adapter.rs b/debugger/src/debug_adapter/dap_adapter.rs index 3a671faa2c..446431c20f 100644 --- a/debugger/src/debug_adapter/dap_adapter.rs +++ b/debugger/src/debug_adapter/dap_adapter.rs @@ -1866,9 +1866,29 @@ impl DebugAdapter

{ } pub fn send_error_response(&mut self, response: &DebuggerError) -> Result<()> { + let expanded_error = { + let mut response_message = response.to_string(); + let mut offset_iterations = 0; + let mut child_error: Option<&dyn std::error::Error> = + std::error::Error::source(&response); + while let Some(source_error) = child_error { + offset_iterations += 1; + response_message = format!("{}\n", response_message,); + for _offset_counter in 0..offset_iterations { + response_message = format!("{}\t", response_message); + } + response_message = format!( + "{}{:?}", + response_message, + ::to_string(source_error) + ); + child_error = std::error::Error::source(source_error); + } + response_message + }; if self .adapter - .show_message(MessageSeverity::Error, response.to_string()) + .show_message(MessageSeverity::Error, expanded_error) { Ok(()) } else { From d58cd92838514b6b20022d5b035039abab24ed04 Mon Sep 17 00:00:00 2001 From: JackN Date: Thu, 7 Apr 2022 11:31:56 -0400 Subject: [PATCH 19/29] Adjust to changes introduced by rebase --- cli/src/debugger.rs | 2 +- debugger/src/debug_adapter/dap_adapter.rs | 101 ++++++++++++---------- debugger/src/debugger/debug_entry.rs | 4 +- debugger/src/peripherals/svd_variables.rs | 44 ++++++---- probe-rs/src/debug/mod.rs | 11 ++- probe-rs/src/debug/variable.rs | 97 +++++++++++++-------- 6 files changed, 148 insertions(+), 111 deletions(-) diff --git a/cli/src/debugger.rs b/cli/src/debugger.rs index aac461e43f..99a689e84c 100644 --- a/cli/src/debugger.rs +++ b/cli/src/debugger.rs @@ -392,7 +392,7 @@ impl DebugCli { println!( "{}: {} = {}", child.name, - child.type_name.display(), + child.type_name, child.get_value(local_variable_cache) ); } diff --git a/debugger/src/debug_adapter/dap_adapter.rs b/debugger/src/debug_adapter/dap_adapter.rs index 446431c20f..bdf1c2aee0 100644 --- a/debugger/src/debug_adapter/dap_adapter.rs +++ b/debugger/src/debug_adapter/dap_adapter.rs @@ -8,7 +8,10 @@ use dap_types::*; use num_traits::Zero; use parse_int::parse; use probe_rs::{ - debug::{ColumnType, Registers, SourceLocation, VariableCache, VariableName, VariableNodeType}, + debug::{ + ColumnType, Registers, SourceLocation, SteppingMode, VariableCache, VariableName, + VariableNodeType, + }, CoreStatus, HaltReason, MemoryInterface, }; use probe_rs_cli_util::rtt; @@ -358,10 +361,7 @@ impl DebugAdapter

{ indexed_child_variables_cnt, ) = self.get_variable_reference(&variable, variable_cache); response_body.indexed_variables = Some(indexed_child_variables_cnt); - - if let VariableLocation::Address(address) = variable.memory_location { - response_body.memory_reference = Some(format!("{:#010x}", address)); - } + response_body.memory_reference = Some(format!("{}", variable.memory_location)); response_body.named_variables = Some(named_child_variables_cnt); response_body.result = variable.get_value(variable_cache); response_body.type_ = Some(format!("{:?}", variable.type_name)); @@ -669,7 +669,7 @@ impl DebugAdapter

{ }; let (verified_breakpoint, reason_msg) = if let Some(source_path) = source_path { - match core_data.debug_info.get_breakpoint_location( + match target_core.core_data.debug_info.get_breakpoint_location( source_path, requested_breakpoint_line, requested_breakpoint_column, @@ -678,7 +678,7 @@ impl DebugAdapter

{ if let Some(breakpoint_address) = valid_breakpoint_location.first_halt_address { - match core_data.set_breakpoint( + match target_core.set_breakpoint( breakpoint_address as u32, BreakpointType::SourceBreakpoint, ) { @@ -1027,7 +1027,11 @@ impl DebugAdapter

{ .0 } else if total_frames > 20 && start_frame + levels > total_frames { // The MS DAP spec may also ask for more frames than what we reported. - core_data.stack_frames.split_at(start_frame as usize).1 + target_core + .core_data + .stack_frames + .split_at(start_frame as usize) + .1 } else { return self.send_response::<()>( request, @@ -1490,11 +1494,17 @@ impl DebugAdapter

{ }, // We use fully qualified Peripheral.Register.Field form to ensure the `evaluate` request can find the right registers and fields by name. evaluate_name: Some(variable.name.to_string()), - memory_reference: Some(format!("{:#010x}", variable.memory_location)), + memory_reference: Some(format!( + "{:#010x}", + variable + .memory_location + .memory_address() + .unwrap_or(u32::MAX) + )), indexed_variables: Some(indexed_child_variables_cnt), named_variables: Some(named_child_variables_cnt), presentation_hint: None, - type_: Some(variable.type_name.clone()), + type_: Some(variable.type_name.to_string()), value: { // The SVD cache is not automatically refreshed on every stack trace, and we only need to refresh the field values. variable.extract_value( @@ -1626,21 +1636,13 @@ impl DebugAdapter

{ named_child_variables_cnt, indexed_child_variables_cnt, ) = self.get_variable_reference(variable, variable_cache); - - let memory_reference = - if let VariableLocation::Address(address) = &variable.memory_location { - Some(format!("{:#x}", address)) - } else { - None - }; - Variable { name: variable.name.to_string(), // evaluate_name: Some(variable.name.to_string()), // Do NOT use evaluate_name. It is impossible to distinguish between duplicate variable // TODO: Implement qualified names. evaluate_name: None, - memory_reference: Some(format!("{:#010x}", variable.memory_location)), + memory_reference: Some(variable.memory_location.to_string()), indexed_variables: Some(indexed_child_variables_cnt), named_variables: Some(named_child_variables_cnt), presentation_hint: None, @@ -1718,7 +1720,7 @@ impl DebugAdapter

{ /// Steps through the code at the requested granularity. /// - [SteppingMode::StepInstruction]: If MS DAP [SteppingGranularity::Instruction] (usually sent from the disassembly view) /// - [SteppingMode::OverStatement]: In all other cases. - pub(crate) fn next(&mut self, core_data: &mut CoreData, request: Request) -> Result<()> { + pub(crate) fn next(&mut self, target_core: &mut CoreHandle, request: Request) -> Result<()> { let arguments: NextArguments = get_arguments(&request)?; let stepping_granularity = match arguments.granularity { @@ -1726,26 +1728,30 @@ impl DebugAdapter

{ _ => SteppingMode::OverStatement, }; - self.debug_step(stepping_granularity, core_data, request) + self.debug_step(stepping_granularity, target_core, request) } /// Steps through the code at the requested granularity. /// - [SteppingMode::StepInstruction]: If MS DAP [SteppingGranularity::Instruction] (usually sent from the disassembly view) /// - [SteppingMode::IntoStatement]: In all other cases. - pub(crate) fn step_in(&mut self, core_data: &mut CoreData, request: Request) -> Result<()> { + pub(crate) fn step_in(&mut self, target_core: &mut CoreHandle, request: Request) -> Result<()> { let arguments: StepInArguments = get_arguments(&request)?; let stepping_granularity = match arguments.granularity { Some(SteppingGranularity::Instruction) => SteppingMode::StepInstruction, _ => SteppingMode::IntoStatement, }; - self.debug_step(stepping_granularity, core_data, request) + self.debug_step(stepping_granularity, target_core, request) } /// Steps through the code at the requested granularity. /// - [SteppingMode::StepInstruction]: If MS DAP [SteppingGranularity::Instruction] (usually sent from the disassembly view) /// - [SteppingMode::OutOfStatement]: In all other cases. - pub(crate) fn step_out(&mut self, core_data: &mut CoreData, request: Request) -> Result<()> { + pub(crate) fn step_out( + &mut self, + target_core: &mut CoreHandle, + request: Request, + ) -> Result<()> { let arguments: StepOutArguments = get_arguments(&request)?; let stepping_granularity = match arguments.granularity { @@ -1753,36 +1759,37 @@ impl DebugAdapter

{ _ => SteppingMode::OutOfStatement, }; - self.debug_step(stepping_granularity, core_data, request) + self.debug_step(stepping_granularity, target_core, request) } /// Common code for the `next`, `step_in`, and `step_out` methods. fn debug_step( &mut self, stepping_granularity: SteppingMode, - core_data: &mut CoreData, + target_core: &mut CoreHandle, request: Request, ) -> Result<(), anyhow::Error> { - let (new_status, program_counter) = - match stepping_granularity.step(&mut core_data.target_core, core_data.debug_info) { - Ok((new_status, program_counter)) => (new_status, program_counter), - Err(error) => match &error { - probe_rs::debug::DebugError::NoValidHaltLocation { - message, - pc_at_error, - } => { - self.show_message( - MessageSeverity::Information, - format!("Step error @{:#010X}: {}", pc_at_error, message), - ); - (core_data.target_core.status()?, *pc_at_error as u32) - } - other_error => { - core_data.target_core.halt(Duration::from_millis(100)).ok(); - return Err(anyhow!("Unexpected error during stepping :{}", other_error)); - } - }, - }; + let (new_status, program_counter) = match stepping_granularity + .step(&mut target_core.core, &target_core.core_data.debug_info) + { + Ok((new_status, program_counter)) => (new_status, program_counter), + Err(error) => match &error { + probe_rs::debug::DebugError::NoValidHaltLocation { + message, + pc_at_error, + } => { + self.show_message( + MessageSeverity::Information, + format!("Step error @{:#010X}: {}", pc_at_error, message), + ); + (target_core.core.status()?, *pc_at_error as u32) + } + other_error => { + target_core.core.halt(Duration::from_millis(100)).ok(); + return Err(anyhow!("Unexpected error during stepping :{}", other_error)); + } + }, + }; self.last_known_status = new_status; self.send_response::<()>(request, Ok(None))?; @@ -1794,7 +1801,7 @@ impl DebugAdapter

{ new_status.short_long_status().1, program_counter )), - thread_id: Some(core_data.target_core.id() as i64), + thread_id: Some(target_core.core.id() as i64), preserve_focus_hint: None, text: None, all_threads_stopped: Some(false), // TODO: Implement multi-core logic here diff --git a/debugger/src/debugger/debug_entry.rs b/debugger/src/debugger/debug_entry.rs index e37bc3d595..82872651cd 100644 --- a/debugger/src/debugger/debug_entry.rs +++ b/debugger/src/debugger/debug_entry.rs @@ -306,10 +306,10 @@ impl Debugger { .next(&mut target_core, request) .and(Ok(DebuggerStatus::ContinueSession)), "stepIn" => debug_adapter - .step_in(&mut core_data, request) + .step_in(&mut target_core, request) .and(Ok(DebuggerStatus::ContinueSession)), "stepOut" => debug_adapter - .step_out(&mut core_data, request) + .step_out(&mut target_core, request) .and(Ok(DebuggerStatus::ContinueSession)), "pause" => debug_adapter .pause(&mut target_core, request) diff --git a/debugger/src/peripherals/svd_variables.rs b/debugger/src/peripherals/svd_variables.rs index dd809c04ae..7821143c06 100644 --- a/debugger/src/peripherals/svd_variables.rs +++ b/debugger/src/peripherals/svd_variables.rs @@ -3,7 +3,9 @@ use crate::{ DebuggerError, }; use probe_rs::{ - debug::{Variable, VariableCache, VariableName, VariableNodeType}, + debug::{ + Variable, VariableCache, VariableLocation, VariableName, VariableNodeType, VariableType, + }, Core, }; use std::{fmt::Debug, fs::File, io::Read, path::Path}; @@ -77,7 +79,7 @@ pub(crate) fn variable_cache_from_svd( ) -> Result { let mut svd_cache = probe_rs::debug::VariableCache::new(); let mut device_root_variable = Variable::new(None, None); - device_root_variable.variable_node_type = probe_rs::debug::VariableNodeType::DoNotRecurse; + device_root_variable.variable_node_type = VariableNodeType::DoNotRecurse; device_root_variable.name = VariableName::PeripheralScopeRoot; device_root_variable = svd_cache.cache_variable(None, device_root_variable, core)?; // Adding the Peripheral Group Name as an additional level in the structure helps to keep the 'variable tree' more compact, but more importantly, it helps to avoid having duplicate variable names that conflict with hal crates. @@ -91,7 +93,8 @@ pub(crate) fn variable_cache_from_svd( if variable_group_name != peripheral_group_name { peripheral_group_variable = Variable::new(None, None); peripheral_group_variable.name = VariableName::Named(peripheral_group_name.clone()); - peripheral_group_variable.type_name = "Peripheral Group".to_string(); + peripheral_group_variable.type_name = + VariableType::Other("Peripheral Group".to_string()); peripheral_group_variable.variable_node_type = VariableNodeType::SvdPeripheral; peripheral_group_variable.set_value(probe_rs::debug::VariableValue::Valid( peripheral @@ -124,9 +127,10 @@ pub(crate) fn variable_cache_from_svd( peripheral_group_variable.name.clone(), peripheral.name.clone() )); - peripheral_variable.type_name = "Peripheral".to_string(); - peripheral_variable.variable_node_type = probe_rs::debug::VariableNodeType::SvdPeripheral; - peripheral_variable.memory_location = peripheral.base_address; + peripheral_variable.type_name = VariableType::Other("Peripheral".to_string()); + peripheral_variable.variable_node_type = VariableNodeType::SvdPeripheral; + peripheral_variable.memory_location = + VariableLocation::Address(peripheral.base_address as u32); peripheral_variable.set_value(probe_rs::debug::VariableValue::Valid( peripheral .description @@ -142,13 +146,15 @@ pub(crate) fn variable_cache_from_svd( &peripheral_variable.name, register.name.clone() )); - register_variable.type_name = register - .description - .clone() - .unwrap_or_else(|| "Peripheral Register".to_string()); - register_variable.variable_node_type = probe_rs::debug::VariableNodeType::SvdRegister; + register_variable.type_name = VariableType::Other( + register + .description + .clone() + .unwrap_or_else(|| "Peripheral Register".to_string()), + ); + register_variable.variable_node_type = VariableNodeType::SvdRegister; register_variable.memory_location = - peripheral.base_address + register.address_offset as u64; + VariableLocation::Address(peripheral.base_address as u32 + register.address_offset); let mut register_has_restricted_read = false; if register.read_action.is_some() || (if let Some(register_access) = register.properties.access { @@ -174,12 +180,14 @@ pub(crate) fn variable_cache_from_svd( ®ister_variable.name, field.name.clone() )); - field_variable.type_name = field - .description - .clone() - .unwrap_or_else(|| "Register Field".to_string()); - field_variable.variable_node_type = probe_rs::debug::VariableNodeType::SvdField; - field_variable.memory_location = register_variable.memory_location; + field_variable.type_name = VariableType::Other( + field + .description + .clone() + .unwrap_or_else(|| "Register Field".to_string()), + ); + field_variable.variable_node_type = VariableNodeType::SvdField; + field_variable.memory_location = register_variable.memory_location.clone(); // For SVD fields, we overload the range_lower_bound and range_upper_bound as the bit range LSB and MSB. field_variable.range_lower_bound = field.bit_offset() as i64; field_variable.range_upper_bound = (field.bit_offset() + field.bit_width()) as i64; diff --git a/probe-rs/src/debug/mod.rs b/probe-rs/src/debug/mod.rs index 9bc8f5fe3d..829eb2cb5a 100644 --- a/probe-rs/src/debug/mod.rs +++ b/probe-rs/src/debug/mod.rs @@ -12,7 +12,6 @@ mod variable; use crate::{ core::{Core, RegisterFile}, - debug::variable::VariableType, CoreStatus, MemoryInterface, }; use gimli::{ @@ -33,7 +32,8 @@ use std::{ vec, }; pub use variable::{ - Variable, VariableCache, VariableName, VariableNodeType, VariableValue, VariantRole, + Variable, VariableCache, VariableLocation, VariableName, VariableNodeType, VariableType, + VariableValue, VariantRole, }; /// An error occurred while debugging the target. @@ -894,8 +894,8 @@ impl DebugInfo { variable::VariableLocation::Address(memory_location) } Err(error) => { - log::error!("Failed to read referenced variable address from memory location {:#010x?} : {}.", parent_variable.memory_location , error); - variable::VariableLocation::Error(format!("Failed to read referenced variable address from memory location {:#010x?} : {}.", parent_variable.memory_location, error)) + log::error!("Failed to read referenced variable address from memory location {} : {}.", parent_variable.memory_location , error); + variable::VariableLocation::Error(format!("Failed to read referenced variable address from memory location {} : {}.", parent_variable.memory_location, error)) } }; } @@ -3042,8 +3042,7 @@ impl<'debuginfo> UnitInfo<'debuginfo> { }; child_variable.set_value(VariableValue::Valid(format!( "{}::{}", - child_variable.type_name.display(), - enumumerator_value + child_variable.type_name, enumumerator_value ))); // We don't need to keep these children. cache.remove_cache_entry_children(child_variable.variable_key)?; diff --git a/probe-rs/src/debug/variable.rs b/probe-rs/src/debug/variable.rs index fdec990562..32f25be1b4 100644 --- a/probe-rs/src/debug/variable.rs +++ b/probe-rs/src/debug/variable.rs @@ -415,20 +415,30 @@ impl Default for VariableNodeType { } } +/// The variants of VariableType allows us to streamline the conditional logic that requires specific handling depending on the nature of the variable. #[derive(Debug, Clone, PartialEq)] pub enum VariableType { + /// A variable with a Rust base datatype. Base(String), + /// A Rust struct. Struct(String), + /// A Rust enum. Enum(String), + /// Namespace refers to the path that qualifies a variable. e.g. "std::string" is the namespace for the strucct "String" Namespace, + /// A Pointer is a variable that contains a reference to another variable Pointer(Option), + /// A Rust array. Array { // TODO: Use a proper type here, not variable name + /// The name of the variable. entry_type: VariableName, + /// The number of entries in the array. count: usize, }, - Unnamed, + /// When we are unable to determine the name of a variable. Unknown, + /// For infrequently used categories of variables that does not fall into any of the other VriableType variants. Other(String), } @@ -439,6 +449,7 @@ impl Default for VariableType { } impl VariableType { + /// A Rust PhantomData type used as a marker for to "act like" they own a specific type. pub fn is_phantom_data(&self) -> bool { match self { VariableType::Struct(name) => name.starts_with("PhantomData"), @@ -446,6 +457,7 @@ impl VariableType { } } + /// This variable is a reference to another variable. pub fn is_reference(&self) -> bool { match self { VariableType::Pointer(Some(name)) => name.starts_with('&'), @@ -453,23 +465,26 @@ impl VariableType { } } + /// This variable is an array, and requires special processing during pub fn is_array(&self) -> bool { matches!(self, VariableType::Array { .. }) } +} - pub fn display(&self) -> String { +impl std::fmt::Display for VariableType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - VariableType::Base(base) => base.clone(), - VariableType::Struct(struct_name) => struct_name.clone(), - VariableType::Enum(enum_name) => enum_name.clone(), - VariableType::Namespace => "".to_string(), + VariableType::Base(base) => base.fmt(f), + VariableType::Struct(struct_name) => struct_name.fmt(f), + VariableType::Enum(enum_name) => enum_name.fmt(f), + VariableType::Namespace => "".fmt(f), VariableType::Pointer(pointer_name) => pointer_name .clone() - .unwrap_or_else(|| "".to_string()), - VariableType::Array { entry_type, count } => format!("[{}; {}]", entry_type, count), - VariableType::Unnamed => "".to_string(), - VariableType::Unknown => "".to_string(), - VariableType::Other(other) => other.clone(), + .unwrap_or_else(|| "".to_string()) + .fmt(f), + VariableType::Array { entry_type, count } => write!(f, "[{}; {}]", entry_type, count), + VariableType::Unknown => "".fmt(f), + VariableType::Other(other) => other.fmt(f), } } } @@ -517,6 +532,20 @@ impl VariableLocation { } } +impl std::fmt::Display for VariableLocation { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + VariableLocation::Unknown => "".fmt(f), + VariableLocation::Unavailable => "".fmt(f), + VariableLocation::Address(address) => write!(f, "{:#010X}", address), + VariableLocation::Register(register) => write!(f, "r{}", register), + VariableLocation::Value => "".fmt(f), + VariableLocation::Error(error) => error.fmt(f), + VariableLocation::Unsupported(reason) => reason.fmt(f), + } + } +} + impl Default for VariableLocation { fn default() -> Self { VariableLocation::Unknown @@ -685,7 +714,8 @@ impl Variable { if let Ok(register_u32_value) = register_value.parse::() { format!( "{:032b} @ {:#010X}", - register_u32_value, self.memory_location + register_u32_value, + self.memory_location.memory_address().unwrap_or(u32::MAX) // We should never encounter a memory location that is invalid if we already used it to read the register value. ) } else { format!("Invalid register value {}", register_value) @@ -703,7 +733,7 @@ impl Variable { format!( "{:0width$b} @ {:#010X}:{}..{}", bit_value, - self.memory_location, + self.memory_location.memory_address().unwrap_or(u32::MAX), self.range_lower_bound, self.range_upper_bound, width = (self.range_upper_bound - self.range_lower_bound) as usize @@ -772,33 +802,30 @@ impl Variable { { // Special handling for SVD registers. // Because we cache the SVD structure once per sesion, we have to re-read the actual register values whenever queried. - match core.read_word_32(self.memory_location as u32) { + match core.read_word_32(self.memory_location.memory_address().unwrap_or(u32::MAX)) { Ok(u32_value) => self.value = VariableValue::Valid(u32_value.to_le().to_string()), Err(error) => { self.value = VariableValue::Error(format!( "Unable to read peripheral register value @ {:#010X} : {:?}", - self.memory_location, error + self.memory_location.memory_address().unwrap_or(u32::MAX), + error )) } } return; } else if !self.value.is_empty() // The value was set explicitly, so just leave it as is, or it was an error, so don't attempt anything else - || self.memory_location == u64::MAX - // Early on in the process of `Variable` evaluation + || !self.memory_location.valid() + // This may just be that we are early on in the process of `Variable` evaluation || self.type_name == VariableType::Unknown + // This may just be that we are early on in the process of `Variable` evaluation { // Quick exit if we don't really need to do much more. return; } else if self.variable_node_type.is_deferred() { // And we have not previously assigned the value, then assign the type and address as the value - let location = match &self.memory_location { - VariableLocation::Address(address) => format!("{:#010X}", address), - other => format!("{:?}", other), - }; - self.value = - VariableValue::Valid(format!("{} @ {}", self.type_name.display(), location)); + VariableValue::Valid(format!("{} @ {}", self.type_name, self.memory_location)); return; } @@ -933,11 +960,7 @@ impl Variable { // Use the supplied value or error message. format!( "{}{:\t Date: Thu, 7 Apr 2022 13:37:39 -0400 Subject: [PATCH 20/29] Fix disassembly request read pointer underflow --- debugger/src/debug_adapter/dap_adapter.rs | 26 +++++++++++++++++------ probe-rs/src/debug/mod.rs | 3 ++- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/debugger/src/debug_adapter/dap_adapter.rs b/debugger/src/debug_adapter/dap_adapter.rs index bdf1c2aee0..a0bd11d084 100644 --- a/debugger/src/debug_adapter/dap_adapter.rs +++ b/debugger/src/debug_adapter/dap_adapter.rs @@ -1263,10 +1263,17 @@ impl DebugAdapter

{ // Control whether we need to read target memory in order to disassemble the next instruction. let mut read_more_bytes = true; - // The memory address for the next read from target memory. We have to manually adjust it to be word aligned. - let mut read_pointer = - (memory_reference + byte_offset + instruction_offset_as_bytes) as u32; - read_pointer = read_pointer - read_pointer % 4; + // The memory address for the next read from target memory. We have to manually adjust it to be word aligned, and make sure it doesn't underflow/overflow. + let mut read_pointer = if byte_offset.is_negative() { + memory_reference.saturating_sub(byte_offset.abs()) + } else { + memory_reference.saturating_add(byte_offset) + } as u32; + read_pointer = if instruction_offset_as_bytes.is_negative() { + read_pointer.saturating_sub(instruction_offset_as_bytes.abs() as u32) + } else { + read_pointer.saturating_add(instruction_offset_as_bytes as u32) + }; // The memory address for the next instruction to be disassembled let mut instruction_pointer = read_pointer as u64; @@ -1280,7 +1287,14 @@ impl DebugAdapter

{ match target_core.core.read_word_32(read_pointer) { Ok(new_word) => { // Advance the read pointer for next time we need it. - read_pointer += 4; + read_pointer = if let Some(valid_read_pointer) = read_pointer.checked_add(4) + { + valid_read_pointer + } else { + // If this happens, the next loop will generate "invalid instruction" records. + read_pointer = u32::MAX; + continue; + }; // Update the code buffer. for new_byte in new_word.to_le_bytes() { code_buffer.push(new_byte); @@ -1288,7 +1302,7 @@ impl DebugAdapter

{ } Err(_) => { // If we can't read data at a given address, then create a "invalid instruction" record, and keep trying. - read_pointer += 4; + read_pointer = read_pointer.saturating_add(4); assembly_lines.push(dap_types::DisassembledInstruction { address: format!("{:#010X}", read_pointer), column: None, diff --git a/probe-rs/src/debug/mod.rs b/probe-rs/src/debug/mod.rs index 829eb2cb5a..f251284f06 100644 --- a/probe-rs/src/debug/mod.rs +++ b/probe-rs/src/debug/mod.rs @@ -1503,7 +1503,8 @@ impl DebugInfo { ) { Ok(frame_descriptor_entry) => frame_descriptor_entry, Err(error) => { - log::error!( + // This is not an error, it just means we cannot unwind any deeper. + log::debug!( "UNWIND: Error reading previous FrameDescriptorEntry at PC={:#010x} : {}", previous_frame_pc, error From d65bb651211db9a40e3d899e441ccf29c461a502 Mon Sep 17 00:00:00 2001 From: JackN Date: Thu, 7 Apr 2022 13:45:06 -0400 Subject: [PATCH 21/29] MS DAP debug protocol update to 1.55.1 --- debugger/src/debug_adapter/debugProtocol.json | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/debugger/src/debug_adapter/debugProtocol.json b/debugger/src/debug_adapter/debugProtocol.json index e331ac604a..aa70609e47 100644 --- a/debugger/src/debug_adapter/debugProtocol.json +++ b/debugger/src/debug_adapter/debugProtocol.json @@ -1035,7 +1035,7 @@ "DisconnectRequest": { "allOf": [ { "$ref": "#/definitions/Request" }, { "type": "object", - "description": "The 'disconnect' request is sent from the client to the debug adapter in order to stop debugging.\nIt asks the debug adapter to disconnect from the debuggee and to terminate the debug adapter.\nIf the debuggee has been started with the 'launch' request, the 'disconnect' request terminates the debuggee.\nIf the 'attach' request was used to connect to the debuggee, 'disconnect' does not terminate the debuggee.\nThis behavior can be controlled with the 'terminateDebuggee' argument (if supported by the debug adapter).", + "description": "The 'disconnect' request asks the debug adapter to disconnect from the debuggee (thus ending the debug session) and then to shut down itself (the debug adapter).\nIn addition, the debug adapter must terminate the debuggee if it was started with the 'launch' request. If an 'attach' request was used to connect to the debuggee, then the debug adapter must not terminate the debuggee.\nThis implicit behavior of when to terminate the debuggee can be overridden with the optional argument 'terminateDebuggee' (which is only supported by a debug adapter if the corresponding capability 'supportTerminateDebuggee' is true).", "properties": { "command": { "type": "string", @@ -1076,7 +1076,7 @@ "TerminateRequest": { "allOf": [ { "$ref": "#/definitions/Request" }, { "type": "object", - "description": "The 'terminate' request is sent from the client to the debug adapter in order to give the debuggee a chance for terminating itself.\nClients should only call this request if the capability 'supportsTerminateRequest' is true.", + "description": "The 'terminate' request is sent from the client to the debug adapter in order to shut down the debuggee gracefully. Clients should only call this request if the capability 'supportsTerminateRequest' is true.\nTypically a debug adapter implements 'terminate' by sending a software signal which the debuggee intercepts in order to clean things up properly before terminating itself.\nPlease note that this request does not directly affect the state of the debug session: if the debuggee decides to veto the graceful shutdown for any reason by not terminating itself, then the debug session will just continue.\nClients can surface the 'terminate' request as an explicit command or they can integrate it into a two stage Stop command that first sends 'terminate' to request a graceful shutdown, and if that fails uses 'disconnect' for a forceful shutdown.", "properties": { "command": { "type": "string", @@ -1195,7 +1195,7 @@ "properties": { "source": { "$ref": "#/definitions/Source", - "description": "The source location of the breakpoints; either 'source.path' or 'source.reference' must be specified." + "description": "The source location of the breakpoints; either 'source.path' or 'source.sourceReference' must be specified." }, "breakpoints": { "type": "array", @@ -2417,18 +2417,19 @@ }, "context": { "type": "string", - "_enum": [ "watch", "repl", "hover", "clipboard" ], + "_enum": [ "variables", "watch", "repl", "hover", "clipboard" ], "enumDescriptions": [ - "evaluate is run in a watch.", - "evaluate is run from REPL console.", - "evaluate is run from a data hover.", - "evaluate is run to generate the value that will be stored in the clipboard.\nThe attribute is only honored by a debug adapter if the capability 'supportsClipboardContext' is true." + "evaluate is called from a variables view context.", + "evaluate is called from a watch view context.", + "evaluate is called from a REPL context.", + "evaluate is called to generate the debug hover contents.\nThis value should only be used if the capability 'supportsEvaluateForHovers' is true.", + "evaluate is called to generate clipboard contents.\nThis value should only be used if the capability 'supportsClipboardContext' is true." ], - "description": "The context in which the evaluate request is run." + "description": "The context in which the evaluate request is used." }, "format": { "$ref": "#/definitions/ValueFormat", - "description": "Specifies details on how to format the Evaluate result.\nThe attribute is only honored by a debug adapter if the capability 'supportsValueFormattingOptions' is true." + "description": "Specifies details on how to format the result.\nThe attribute is only honored by a debug adapter if the capability 'supportsValueFormattingOptions' is true." } }, "required": [ "expression" ] @@ -3940,7 +3941,7 @@ "ExceptionFilterOptions": { "type": "object", - "description": "An ExceptionFilterOptions is used to specify an exception filter together with a condition for the setExceptionsFilter request.", + "description": "An ExceptionFilterOptions is used to specify an exception filter together with a condition for the 'setExceptionBreakpoints' request.", "properties": { "filterId": { "type": "string", From 62cc4536cd7c8b35e1c72bb3e4761bada2a04aaa Mon Sep 17 00:00:00 2001 From: JackN Date: Thu, 7 Apr 2022 13:52:42 -0400 Subject: [PATCH 22/29] Add PR # in CHANGELOG --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11ebbb6ce7..b49310629e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Debugger: Add support for DAP Requests (ReadMemory, WriteMemory, Evaluate & SetVariable) (#1035) - Debugger: Add support for DAP Requests (Disassemble & SetInstructionBreakpoints) (#1049) - Debugger: Add support for stepping at 'statement' level, plus 'step in', 'step out' (#1056) -- Debugger: Add support for navigating and monitoring SVD Peripheral Registers. () +- Debugger: Add support for navigating and monitoring SVD Peripheral Registers. (#1072) ### Changed @@ -44,7 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated STM32H7 series yaml to support newly released chips. (#1011) - Debugger: Removed the CLI mode, in favour of `probe-rs-cli` which has richer functionality. (#1041) - Renamed `Probe::speed` to `Probe::speed_khz`. -- Debugger: Requires changes to DAP Client `launch.json` to prepare for WIP multi-core support. () +- Debugger: Requires changes to DAP Client `launch.json` to prepare for WIP multi-core support. (#1072) ### Fixed From a95e81312852163b96b9b8895cc43aa618b1e7de Mon Sep 17 00:00:00 2001 From: JackN Date: Thu, 7 Apr 2022 14:46:29 -0400 Subject: [PATCH 23/29] Remove old/obsolete snapshots --- ...r__test__receive_request_with_invalid_json.snap | 14 -------------- ..._receive_request_with_wrong_content_length.snap | 14 -------------- ...adapter__test__receive_request_would_block.snap | 6 ------ ...debug_adapter__test__receive_valid_request.snap | 8 -------- 4 files changed, 42 deletions(-) delete mode 100644 debugger/src/snapshots/probe_rs_debugger__debug_adapter__test__receive_request_with_invalid_json.snap delete mode 100644 debugger/src/snapshots/probe_rs_debugger__debug_adapter__test__receive_request_with_wrong_content_length.snap delete mode 100644 debugger/src/snapshots/probe_rs_debugger__debug_adapter__test__receive_request_would_block.snap delete mode 100644 debugger/src/snapshots/probe_rs_debugger__debug_adapter__test__receive_valid_request.snap diff --git a/debugger/src/snapshots/probe_rs_debugger__debug_adapter__test__receive_request_with_invalid_json.snap b/debugger/src/snapshots/probe_rs_debugger__debug_adapter__test__receive_request_with_invalid_json.snap deleted file mode 100644 index f1f2cdcd65..0000000000 --- a/debugger/src/snapshots/probe_rs_debugger__debug_adapter__test__receive_request_with_invalid_json.snap +++ /dev/null @@ -1,14 +0,0 @@ ---- -source: debugger/src/debug_adapter.rs -expression: output_str - ---- -Content-Length: 136 - -{"command":"error","message":"EOF while parsing a string at line 1 column 49","request_seq":1,"seq":1,"success":false,"type":"response"}Content-Length: 156 - -{"body":{"category":"console","group":"probe-rs-debug","output":"EOF while parsing a string at line 1 column 49\n"},"event":"output","seq":2,"type":"event"}Content-Length: 145 - -{"body":{"message":"EOF while parsing a string at line 1 column 49\n","severity":"error"},"event":"probe-rs-show-message","seq":3,"type":"event"}Content-Length: 154 - -{"body":{"category":"console","group":"probe-rs-debug","output":"\nTriggered DAP Event: probe-rs-show-message\n"},"event":"output","seq":4,"type":"event"} diff --git a/debugger/src/snapshots/probe_rs_debugger__debug_adapter__test__receive_request_with_wrong_content_length.snap b/debugger/src/snapshots/probe_rs_debugger__debug_adapter__test__receive_request_with_wrong_content_length.snap deleted file mode 100644 index d0d6ccb9d6..0000000000 --- a/debugger/src/snapshots/probe_rs_debugger__debug_adapter__test__receive_request_with_wrong_content_length.snap +++ /dev/null @@ -1,14 +0,0 @@ ---- -source: debugger/src/debug_adapter.rs -expression: output_str - ---- -Content-Length: 145 - -{"command":"error","message":"Failed to read the expected 60 bytes from incoming data","request_seq":1,"seq":1,"success":false,"type":"response"}Content-Length: 165 - -{"body":{"category":"console","group":"probe-rs-debug","output":"Failed to read the expected 60 bytes from incoming data\n"},"event":"output","seq":2,"type":"event"}Content-Length: 154 - -{"body":{"message":"Failed to read the expected 60 bytes from incoming data\n","severity":"error"},"event":"probe-rs-show-message","seq":3,"type":"event"}Content-Length: 154 - -{"body":{"category":"console","group":"probe-rs-debug","output":"\nTriggered DAP Event: probe-rs-show-message\n"},"event":"output","seq":4,"type":"event"} diff --git a/debugger/src/snapshots/probe_rs_debugger__debug_adapter__test__receive_request_would_block.snap b/debugger/src/snapshots/probe_rs_debugger__debug_adapter__test__receive_request_would_block.snap deleted file mode 100644 index d4ef3f4364..0000000000 --- a/debugger/src/snapshots/probe_rs_debugger__debug_adapter__test__receive_request_would_block.snap +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: debugger/src/debug_adapter.rs -expression: output_str - ---- - diff --git a/debugger/src/snapshots/probe_rs_debugger__debug_adapter__test__receive_valid_request.snap b/debugger/src/snapshots/probe_rs_debugger__debug_adapter__test__receive_valid_request.snap deleted file mode 100644 index 74e7b7d8a9..0000000000 --- a/debugger/src/snapshots/probe_rs_debugger__debug_adapter__test__receive_valid_request.snap +++ /dev/null @@ -1,8 +0,0 @@ ---- -source: debugger/src/debug_adapter.rs -expression: output_str - ---- -Content-Length: 151 - -{"body":{"category":"console","group":"probe-rs-debug","output":"\nReceived DAP Request sequence #3 : test\n"},"event":"output","seq":1,"type":"event"} From 3a407d8da5709f9c18acbece4c5620ac6b522c77 Mon Sep 17 00:00:00 2001 From: JackN Date: Thu, 7 Apr 2022 14:55:23 -0400 Subject: [PATCH 24/29] Moved, Renamed and Updated test snapshot files --- ..._protocol__test__receive_request_with_invalid_json.snap} | 3 +-- ...l__test__receive_request_with_wrong_content_length.snap} | 3 +-- ...dapter__protocol__test__receive_request_would_block.snap | 5 +++++ ...bug_adapter__protocol__test__receive_valid_request.snap} | 3 +-- ...bugger__protocol__test__receive_request_would_block.snap | 6 ------ 5 files changed, 8 insertions(+), 12 deletions(-) rename debugger/src/{snapshots/probe_rs_debugger__protocol__test__receive_request_with_invalid_json.snap => debug_adapter/snapshots/probe_rs_debugger__debug_adapter__protocol__test__receive_request_with_invalid_json.snap} (92%) rename debugger/src/{snapshots/probe_rs_debugger__protocol__test__receive_request_with_wrong_content_length.snap => debug_adapter/snapshots/probe_rs_debugger__debug_adapter__protocol__test__receive_request_with_wrong_content_length.snap} (92%) create mode 100644 debugger/src/debug_adapter/snapshots/probe_rs_debugger__debug_adapter__protocol__test__receive_request_would_block.snap rename debugger/src/{snapshots/probe_rs_debugger__protocol__test__receive_valid_request.snap => debug_adapter/snapshots/probe_rs_debugger__debug_adapter__protocol__test__receive_valid_request.snap} (81%) delete mode 100644 debugger/src/snapshots/probe_rs_debugger__protocol__test__receive_request_would_block.snap diff --git a/debugger/src/snapshots/probe_rs_debugger__protocol__test__receive_request_with_invalid_json.snap b/debugger/src/debug_adapter/snapshots/probe_rs_debugger__debug_adapter__protocol__test__receive_request_with_invalid_json.snap similarity index 92% rename from debugger/src/snapshots/probe_rs_debugger__protocol__test__receive_request_with_invalid_json.snap rename to debugger/src/debug_adapter/snapshots/probe_rs_debugger__debug_adapter__protocol__test__receive_request_with_invalid_json.snap index a535aa10c4..bc50f9e283 100644 --- a/debugger/src/snapshots/probe_rs_debugger__protocol__test__receive_request_with_invalid_json.snap +++ b/debugger/src/debug_adapter/snapshots/probe_rs_debugger__debug_adapter__protocol__test__receive_request_with_invalid_json.snap @@ -1,7 +1,6 @@ --- -source: debugger/src/protocol.rs +source: debugger/src/debug_adapter/protocol.rs expression: output_str - --- Content-Length: 156 diff --git a/debugger/src/snapshots/probe_rs_debugger__protocol__test__receive_request_with_wrong_content_length.snap b/debugger/src/debug_adapter/snapshots/probe_rs_debugger__debug_adapter__protocol__test__receive_request_with_wrong_content_length.snap similarity index 92% rename from debugger/src/snapshots/probe_rs_debugger__protocol__test__receive_request_with_wrong_content_length.snap rename to debugger/src/debug_adapter/snapshots/probe_rs_debugger__debug_adapter__protocol__test__receive_request_with_wrong_content_length.snap index 2e64ae4726..ff28dd8bf3 100644 --- a/debugger/src/snapshots/probe_rs_debugger__protocol__test__receive_request_with_wrong_content_length.snap +++ b/debugger/src/debug_adapter/snapshots/probe_rs_debugger__debug_adapter__protocol__test__receive_request_with_wrong_content_length.snap @@ -1,7 +1,6 @@ --- -source: debugger/src/protocol.rs +source: debugger/src/debug_adapter/protocol.rs expression: output_str - --- Content-Length: 165 diff --git a/debugger/src/debug_adapter/snapshots/probe_rs_debugger__debug_adapter__protocol__test__receive_request_would_block.snap b/debugger/src/debug_adapter/snapshots/probe_rs_debugger__debug_adapter__protocol__test__receive_request_would_block.snap new file mode 100644 index 0000000000..c1ec595c3e --- /dev/null +++ b/debugger/src/debug_adapter/snapshots/probe_rs_debugger__debug_adapter__protocol__test__receive_request_would_block.snap @@ -0,0 +1,5 @@ +--- +source: debugger/src/debug_adapter/protocol.rs +expression: output_str +--- + diff --git a/debugger/src/snapshots/probe_rs_debugger__protocol__test__receive_valid_request.snap b/debugger/src/debug_adapter/snapshots/probe_rs_debugger__debug_adapter__protocol__test__receive_valid_request.snap similarity index 81% rename from debugger/src/snapshots/probe_rs_debugger__protocol__test__receive_valid_request.snap rename to debugger/src/debug_adapter/snapshots/probe_rs_debugger__debug_adapter__protocol__test__receive_valid_request.snap index ad3060ae2a..bcec575276 100644 --- a/debugger/src/snapshots/probe_rs_debugger__protocol__test__receive_valid_request.snap +++ b/debugger/src/debug_adapter/snapshots/probe_rs_debugger__debug_adapter__protocol__test__receive_valid_request.snap @@ -1,7 +1,6 @@ --- -source: debugger/src/protocol.rs +source: debugger/src/debug_adapter/protocol.rs expression: output_str - --- Content-Length: 151 diff --git a/debugger/src/snapshots/probe_rs_debugger__protocol__test__receive_request_would_block.snap b/debugger/src/snapshots/probe_rs_debugger__protocol__test__receive_request_would_block.snap deleted file mode 100644 index be05c98255..0000000000 --- a/debugger/src/snapshots/probe_rs_debugger__protocol__test__receive_request_would_block.snap +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: debugger/src/protocol.rs -expression: output_str - ---- - From 3b3d6d5990fb928bb3b4491c1bf95ba9da07fe59 Mon Sep 17 00:00:00 2001 From: JackN Date: Fri, 8 Apr 2022 12:20:03 -0400 Subject: [PATCH 25/29] Use `svd-rs` [derive-from] feature --- debugger/Cargo.toml | 2 +- debugger/src/peripherals/svd_variables.rs | 323 ++++------------------ 2 files changed, 59 insertions(+), 266 deletions(-) diff --git a/debugger/Cargo.toml b/debugger/Cargo.toml index 962dd71589..600e338d19 100644 --- a/debugger/Cargo.toml +++ b/debugger/Cargo.toml @@ -40,7 +40,7 @@ chrono = { version = "0.4", features = ["serde"] } goblin = "0.5.1" base64 = "0.13" svd-parser = "0.13.1" -svd-rs = "0.13.1" +svd-rs = { version = "0.13.1", features = ["derive-from"] } [dev-dependencies] insta = "1.8.0" diff --git a/debugger/src/peripherals/svd_variables.rs b/debugger/src/peripherals/svd_variables.rs index 7821143c06..b07e5db818 100644 --- a/debugger/src/peripherals/svd_variables.rs +++ b/debugger/src/peripherals/svd_variables.rs @@ -9,8 +9,8 @@ use probe_rs::{ Core, }; use std::{fmt::Debug, fs::File, io::Read, path::Path}; -use svd_parser::{self as svd, ValidateLevel}; -use svd_rs::{Access, Device, EnumeratedValues, FieldInfo, PeripheralInfo, RegisterInfo}; +use svd_parser::{self as svd}; +use svd_rs::{Access, DeriveFrom, Device, FieldInfo, PeripheralInfo, RegisterInfo}; /// The SVD file contents and related data #[derive(Debug)] @@ -238,43 +238,16 @@ pub(crate) fn resolve_peripherals( ) -> Result, DebuggerError> { let mut resolved_peripherals = vec![]; for device_peripheral in &peripheral_device.peripherals { - // TODO: Need to code for the impact of MaybeArray results. - let mut peripheral_builder = PeripheralInfo::builder(); if let Some(derived_from) = &device_peripheral.derived_from { - if let Some(template_peripheral) = peripheral_device.get_peripheral(derived_from) { - if template_peripheral.group_name.is_some() { - peripheral_builder = - peripheral_builder.group_name(template_peripheral.group_name.clone()); - } - if template_peripheral.display_name.is_some() { - peripheral_builder = - peripheral_builder.display_name(template_peripheral.display_name.clone()); - } - if template_peripheral.description.is_some() { - peripheral_builder = - peripheral_builder.description(template_peripheral.description.clone()); - } - if template_peripheral.prepend_to_name.is_some() { - peripheral_builder = peripheral_builder - .prepend_to_name(template_peripheral.prepend_to_name.clone()); - } - if template_peripheral.append_to_name.is_some() { - peripheral_builder = peripheral_builder - .append_to_name(template_peripheral.append_to_name.clone()); - } - peripheral_builder = - peripheral_builder.base_address(template_peripheral.base_address); - peripheral_builder = peripheral_builder - .default_register_properties(template_peripheral.default_register_properties); - if template_peripheral.address_block.is_some() { - peripheral_builder = - peripheral_builder.address_block(template_peripheral.address_block.clone()); - } - peripheral_builder = - peripheral_builder.interrupt(Some(template_peripheral.interrupt.clone())); - if template_peripheral.registers.is_some() { - peripheral_builder = - peripheral_builder.registers(template_peripheral.registers.clone()); + if let Some(derived_result) = peripheral_device.get_peripheral(derived_from) { + match &device_peripheral.derive_from(derived_result) { + svd_rs::MaybeArray::Single(derived_peripheral) => { + resolved_peripherals.push(derived_peripheral.clone()); + } + svd_rs::MaybeArray::Array(peripheral_array, _) => { + log::warn!("Unsupported Array in SVD for Peripheral:{}. Only the first instance will be visible.", peripheral_array.name); + resolved_peripherals.push(peripheral_array.clone()); + } } } else { return Err(DebuggerError::Other(anyhow::anyhow!( @@ -282,45 +255,17 @@ pub(crate) fn resolve_peripherals( derived_from ))); }; + } else { + match device_peripheral { + svd_rs::MaybeArray::Single(original_peripheral) => { + resolved_peripherals.push(original_peripheral.clone()) + } + svd_rs::MaybeArray::Array(peripheral_array, _) => { + log::warn!("Unsupported Array in SVD for Peripheral:{}. Only the first instance will be visible.", peripheral_array.name); + resolved_peripherals.push(peripheral_array.clone()); + } + } } - // Irrespective of derived_from values, set the values we need. - peripheral_builder = peripheral_builder.name(device_peripheral.name.clone()); - if device_peripheral.description.is_some() { - peripheral_builder = - peripheral_builder.description(device_peripheral.description.clone()); - } - if device_peripheral.display_name.is_some() { - peripheral_builder = - peripheral_builder.display_name(device_peripheral.display_name.clone()); - } - if device_peripheral.group_name.is_some() { - peripheral_builder = - peripheral_builder.group_name(device_peripheral.group_name.clone()); - } - if device_peripheral.prepend_to_name.is_some() { - peripheral_builder = - peripheral_builder.prepend_to_name(device_peripheral.prepend_to_name.clone()); - } - if device_peripheral.append_to_name.is_some() { - peripheral_builder = - peripheral_builder.append_to_name(device_peripheral.append_to_name.clone()); - } - peripheral_builder = peripheral_builder.base_address(device_peripheral.base_address); - peripheral_builder = peripheral_builder - .default_register_properties(device_peripheral.default_register_properties); - if device_peripheral.address_block.is_some() { - peripheral_builder = - peripheral_builder.address_block(device_peripheral.address_block.clone()); - } - peripheral_builder = - peripheral_builder.interrupt(Some(device_peripheral.interrupt.clone())); - if device_peripheral.registers.is_some() { - peripheral_builder = peripheral_builder.registers(device_peripheral.registers.clone()); - } - let resolved_peripheral = peripheral_builder - .build(ValidateLevel::Weak) - .map_err(|error| DebuggerError::Other(anyhow::anyhow!("{:?}", error)))?; - resolved_peripherals.push(resolved_peripheral); } Ok(resolved_peripherals) } @@ -332,54 +277,16 @@ pub(crate) fn resolve_registers( // TODO: Need to code for the impact of register clusters. let mut resolved_registers = vec![]; for peripheral_register in peripheral.registers() { - // TODO: Need to code for the impact of MaybeArray results. - let mut register_builder = RegisterInfo::builder(); - // Deriving the properties starts from the peripheral level defaults. - let mut register_properties = peripheral.default_register_properties; if let Some(derived_from) = &peripheral_register.derived_from { - if let Some(template_register) = peripheral.get_register(derived_from) { - if template_register.display_name.is_some() { - register_builder = - register_builder.display_name(template_register.display_name.clone()); - } - if template_register.description.is_some() { - register_builder = - register_builder.description(template_register.description.clone()); - } - if template_register.modified_write_values.is_some() { - register_builder = register_builder - .modified_write_values(template_register.modified_write_values); - } - if template_register.write_constraint.is_some() { - register_builder = - register_builder.write_constraint(template_register.write_constraint); - } - if template_register.read_action.is_some() { - register_builder = register_builder.read_action(template_register.read_action); - } - if template_register.fields.is_some() { - register_builder = register_builder.fields(template_register.fields.clone()); - } - // We don't update the register_builder properties directly until the next step. - if template_register.properties.size.is_some() { - register_properties = - register_properties.size(template_register.properties.size); - } - if template_register.properties.access.is_some() { - register_properties = - register_properties.access(template_register.properties.access); - } - if template_register.properties.protection.is_some() { - register_properties = - register_properties.protection(template_register.properties.protection); - } - if template_register.properties.reset_value.is_some() { - register_properties = - register_properties.reset_value(template_register.properties.reset_value); - } - if template_register.properties.reset_mask.is_some() { - register_properties = - register_properties.reset_mask(template_register.properties.reset_mask); + if let Some(derived_result) = peripheral.get_register(derived_from) { + match &peripheral_register.derive_from(derived_result) { + svd_rs::MaybeArray::Single(derived_register) => { + resolved_registers.push(derived_register.clone()) + } + svd_rs::MaybeArray::Array(register_array, _) => { + log::warn!("Unsupported Array in SVD for Register:{}. Only the first instance will be visible.", register_array.name); + resolved_registers.push(register_array.clone()); + } } } else { return Err(DebuggerError::Other(anyhow::anyhow!( @@ -387,65 +294,17 @@ pub(crate) fn resolve_registers( derived_from ))); }; + } else { + match peripheral_register { + svd_rs::MaybeArray::Single(original_register) => { + resolved_registers.push(original_register.clone()) + } + svd_rs::MaybeArray::Array(register_array, _) => { + log::warn!("Unsupported Array in SVD for Register:{}. Only the first instance will be visible.", register_array.name); + resolved_registers.push(register_array.clone()); + } + } } - // Irrespective of derived_from values, set the values we need. - let mut register_name = peripheral_register.name.clone(); - if let Some(prefix) = &peripheral.prepend_to_name { - register_name = format!("{}{}", prefix, register_name); - } - if let Some(suffix) = &peripheral.append_to_name { - register_name = format!("{}{}", register_name, suffix); - } - register_builder = register_builder.name(register_name); - if peripheral_register.display_name.is_some() { - register_builder = - register_builder.display_name(peripheral_register.display_name.clone()); - } - if peripheral_register.description.is_some() { - register_builder = - register_builder.description(peripheral_register.description.clone()); - } - register_builder = register_builder.address_offset(peripheral_register.address_offset); - register_builder = register_builder.properties(peripheral_register.properties); - if peripheral_register.modified_write_values.is_some() { - register_builder = - register_builder.modified_write_values(peripheral_register.modified_write_values); - } - if peripheral_register.write_constraint.is_some() { - register_builder = - register_builder.write_constraint(peripheral_register.write_constraint); - } - if peripheral_register.read_action.is_some() { - register_builder = register_builder.read_action(peripheral_register.read_action); - } - if peripheral_register.fields.is_some() { - register_builder = register_builder.fields(peripheral_register.fields.clone()); - } - // Complete the derive of the register properties. - if peripheral_register.properties.size.is_some() { - register_properties = register_properties.size(peripheral_register.properties.size); - } - if peripheral_register.properties.access.is_some() { - register_properties = register_properties.access(peripheral_register.properties.access); - } - if peripheral_register.properties.protection.is_some() { - register_properties = - register_properties.protection(peripheral_register.properties.protection); - } - if peripheral_register.properties.reset_value.is_some() { - register_properties = - register_properties.reset_value(peripheral_register.properties.reset_value); - } - if peripheral_register.properties.reset_mask.is_some() { - register_properties = - register_properties.reset_mask(peripheral_register.properties.reset_mask); - } - register_builder = register_builder.properties(register_properties); - // Not that the register_builder has been updated, we can build it. - let resolved_register = register_builder - .build(ValidateLevel::Weak) - .map_err(|error| DebuggerError::Other(anyhow::anyhow!("{:?}", error)))?; - resolved_registers.push(resolved_register); } Ok(resolved_registers) } @@ -455,25 +314,16 @@ pub(crate) fn resolve_fields(register: &RegisterInfo) -> Result, // TODO: Need to code for the impact of field clusters. let mut resolved_fields = vec![]; for register_field in register.fields() { - // TODO: Need to code for the impact of MaybeArray results. - let mut field_builder = FieldInfo::builder(); if let Some(derived_from) = ®ister_field.derived_from { - if let Some(template_field) = register.get_field(derived_from) { - if template_field.description.is_some() { - field_builder = field_builder.description(template_field.description.clone()); - } - if template_field.access.is_some() { - field_builder = field_builder.access(template_field.access); - } - if template_field.modified_write_values.is_some() { - field_builder = - field_builder.modified_write_values(template_field.modified_write_values); - } - if template_field.write_constraint.is_some() { - field_builder = field_builder.write_constraint(template_field.write_constraint); - } - if template_field.read_action.is_some() { - field_builder = field_builder.read_action(template_field.read_action); + if let Some(derived_result) = register.get_field(derived_from) { + match ®ister_field.derive_from(derived_result) { + svd_rs::MaybeArray::Single(derived_field) => { + resolved_fields.push(derived_field.clone()) + } + svd_rs::MaybeArray::Array(field_array, _) => { + log::warn!("Unsupported Array in SVD for Field:{}. Only the first instance will be visible.", field_array.name); + resolved_fields.push(field_array.clone()); + } } } else { return Err(DebuggerError::Other(anyhow::anyhow!( @@ -481,74 +331,17 @@ pub(crate) fn resolve_fields(register: &RegisterInfo) -> Result, derived_from ))); }; - } - // Irrespective of derived_from values, set the values we need. - field_builder = field_builder.name(register_field.name.clone()); - if register_field.description.is_some() { - field_builder = field_builder.description(register_field.description.clone()); - } - field_builder = field_builder.bit_range(register_field.bit_range); - field_builder = field_builder.access(register_field.access); - if register_field.modified_write_values.is_some() { - field_builder = - field_builder.modified_write_values(register_field.modified_write_values); - } - if register_field.write_constraint.is_some() { - field_builder = field_builder.write_constraint(register_field.write_constraint); - } - if register_field.read_action.is_some() { - field_builder = field_builder.read_action(register_field.read_action); - } - field_builder = field_builder.enumerated_values(register_field.enumerated_values.clone()); - let resolved_field = field_builder - .build(ValidateLevel::Weak) - .map_err(|error| DebuggerError::Other(anyhow::anyhow!("{:?}", error)))?; - resolved_fields.push(resolved_field); - } - Ok(resolved_fields) -} - -// TODO: Implement using these enumerated values for SVD fields. -#[allow(dead_code)] -/// Resolve all the enumerated values of a field through their (optional) `derived_from` values. -pub(crate) fn enumerated_values(field: FieldInfo) -> Result, DebuggerError> { - // TODO: Need to code for the impact of enumerated value clusters. - let mut enumerated_values = vec![]; - for field_enum_values in &field.enumerated_values { - // TODO: Need to code for the impact of MaybeArray results. - let mut enum_values_builder = EnumeratedValues::builder(); - if let Some(derived_from) = &field_enum_values.derived_from { - if let Some(template_enum_values) = - field.enumerated_values.iter().find(|derived_from_values| { - derived_from_values.name == Some(derived_from.to_owned()) - }) - { - if template_enum_values.name.is_some() { - enum_values_builder = - enum_values_builder.name(template_enum_values.name.clone()); + } else { + match register_field { + svd_rs::MaybeArray::Single(original_field) => { + resolved_fields.push(original_field.clone()) } - if template_enum_values.usage.is_some() { - enum_values_builder = enum_values_builder.usage(template_enum_values.usage); + svd_rs::MaybeArray::Array(field_array, _) => { + log::warn!("Unsupported Array in SVD for Field:{}. Only the first instance will be visible.", field_array.name); + resolved_fields.push(field_array.clone()); } - } else { - return Err(DebuggerError::Other(anyhow::anyhow!( - "Unable to retrieve 'derived_from' SVD field: {:?}", - derived_from - ))); - }; - } - // Irrespective of derived_from values, set the values we need. - if field_enum_values.name.is_some() { - enum_values_builder = enum_values_builder.name(field_enum_values.name.clone()); - } - if field_enum_values.usage.is_some() { - enum_values_builder = enum_values_builder.usage(field_enum_values.usage); + } } - enum_values_builder = enum_values_builder.values(field_enum_values.values.clone()); - let resolved_enum_values = enum_values_builder - .build(ValidateLevel::Weak) - .map_err(|error| DebuggerError::Other(anyhow::anyhow!("{:?}", error)))?; - enumerated_values.push(resolved_enum_values); } - Ok(enumerated_values) + Ok(resolved_fields) } From bb6825beefd9ce075b7feb300d4a8226058fc4c8 Mon Sep 17 00:00:00 2001 From: JackN Date: Fri, 8 Apr 2022 13:16:57 -0400 Subject: [PATCH 26/29] Remove redundant reference and clone. --- debugger/src/peripherals/svd_variables.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/debugger/src/peripherals/svd_variables.rs b/debugger/src/peripherals/svd_variables.rs index b07e5db818..981af92ec1 100644 --- a/debugger/src/peripherals/svd_variables.rs +++ b/debugger/src/peripherals/svd_variables.rs @@ -240,13 +240,13 @@ pub(crate) fn resolve_peripherals( for device_peripheral in &peripheral_device.peripherals { if let Some(derived_from) = &device_peripheral.derived_from { if let Some(derived_result) = peripheral_device.get_peripheral(derived_from) { - match &device_peripheral.derive_from(derived_result) { + match device_peripheral.derive_from(derived_result) { svd_rs::MaybeArray::Single(derived_peripheral) => { - resolved_peripherals.push(derived_peripheral.clone()); + resolved_peripherals.push(derived_peripheral); } svd_rs::MaybeArray::Array(peripheral_array, _) => { log::warn!("Unsupported Array in SVD for Peripheral:{}. Only the first instance will be visible.", peripheral_array.name); - resolved_peripherals.push(peripheral_array.clone()); + resolved_peripherals.push(peripheral_array); } } } else { @@ -279,13 +279,13 @@ pub(crate) fn resolve_registers( for peripheral_register in peripheral.registers() { if let Some(derived_from) = &peripheral_register.derived_from { if let Some(derived_result) = peripheral.get_register(derived_from) { - match &peripheral_register.derive_from(derived_result) { + match peripheral_register.derive_from(derived_result) { svd_rs::MaybeArray::Single(derived_register) => { - resolved_registers.push(derived_register.clone()) + resolved_registers.push(derived_register) } svd_rs::MaybeArray::Array(register_array, _) => { log::warn!("Unsupported Array in SVD for Register:{}. Only the first instance will be visible.", register_array.name); - resolved_registers.push(register_array.clone()); + resolved_registers.push(register_array); } } } else { @@ -316,13 +316,13 @@ pub(crate) fn resolve_fields(register: &RegisterInfo) -> Result, for register_field in register.fields() { if let Some(derived_from) = ®ister_field.derived_from { if let Some(derived_result) = register.get_field(derived_from) { - match ®ister_field.derive_from(derived_result) { + match register_field.derive_from(derived_result) { svd_rs::MaybeArray::Single(derived_field) => { - resolved_fields.push(derived_field.clone()) + resolved_fields.push(derived_field) } svd_rs::MaybeArray::Array(field_array, _) => { log::warn!("Unsupported Array in SVD for Field:{}. Only the first instance will be visible.", field_array.name); - resolved_fields.push(field_array.clone()); + resolved_fields.push(field_array); } } } else { From 5f7ecc6e969aef7aee3044c461fccf70e78758e7 Mon Sep 17 00:00:00 2001 From: JackN Date: Thu, 14 Apr 2022 13:22:30 -0400 Subject: [PATCH 27/29] Add option to omit location from Defmt formatting --- probe-rs-cli-util/src/rtt.rs | 58 ++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/probe-rs-cli-util/src/rtt.rs b/probe-rs-cli-util/src/rtt.rs index 3631f7ef1a..e57c031e87 100644 --- a/probe-rs-cli-util/src/rtt.rs +++ b/probe-rs-cli-util/src/rtt.rs @@ -48,6 +48,12 @@ fn default_channel_formats() -> Vec { vec![] } +/// Used by serde to provide defaults for `RttChannelConfig::show_location` +fn default_include_location() -> bool { + // Setting this to true to allow compatibility with behaviour prior to when this option was introduced. + true +} + #[derive(Debug, Copy, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub enum DataFormat { String, @@ -96,7 +102,12 @@ pub struct RttChannelConfig { pub data_format: DataFormat, #[structopt(skip)] #[serde(default)] + // Control the inclusion of timestamps for DataFormat::String. pub show_timestamps: bool, + #[structopt(skip)] + #[serde(default = "default_include_location")] + // Control the inclusion of source location information for DataFormat::Defmt. + pub show_location: bool, } /// This is the primary interface through which RTT channel data is read and written. Every actual RTT channel has a configuration and buffer that is used for this purpose. @@ -110,6 +121,7 @@ pub struct RttActiveChannel { _input_data: String, rtt_buffer: RttBuffer, show_timestamps: bool, + show_location: bool, } /// A fully configured RttActiveChannel. The configuration will always try to 'default' based on information read from the RTT control block in the binary. Where insufficient information is available, it will use the supplied configuration, with final hardcoded defaults where no other information was available. @@ -119,8 +131,8 @@ impl RttActiveChannel { down_channel: Option, channel_config: Option, ) -> Self { - let full_config = match channel_config { - Some(channel_config) => channel_config, + let full_config = match &channel_config { + Some(channel_config) => channel_config.clone(), None => RttChannelConfig { ..Default::default() // Will set intelligent defaults below ... }, @@ -149,10 +161,15 @@ impl RttActiveChannel { .map(|down| down.name() == Some("defmt")) }) .unwrap_or(false); // If no explicit config is requested, assign a default - let data_format: DataFormat = if defmt_enabled { - DataFormat::Defmt + let (data_format, show_location) = if defmt_enabled { + let show_location = if let Some(channel_config) = channel_config { + channel_config.show_location + } else { + true + }; + (DataFormat::Defmt, show_location) } else { - full_config.data_format + (full_config.data_format, false) }; Self { up_channel, @@ -162,6 +179,7 @@ impl RttActiveChannel { _input_data: String::new(), rtt_buffer: RttBuffer::new(buffer_size), show_timestamps: full_config.show_timestamps, + show_location, } } @@ -240,20 +258,22 @@ impl RttActiveChannel { // verified to exist in the `locs` map. let loc = locs.as_ref().map(|locs| &locs[&frame.index()]); writeln!(formatted_data, "{}", frame.display(false)).map_or_else(|err| log::error!("Failed to format RTT data - {:?}", err), |r|r); - if let Some(loc) = loc { - let relpath = if let Ok(relpath) = - loc.file.strip_prefix(&std::env::current_dir().unwrap()) - { - relpath - } else { - // not relative; use full path - &loc.file - }; - writeln!(formatted_data, - "└─ {}:{}", - relpath.display(), - loc.line - ).map_or_else(|err| log::error!("Failed to format RTT data - {:?}", err), |r|r); + if self.show_location { + if let Some(loc) = loc { + let relpath = if let Ok(relpath) = + loc.file.strip_prefix(&std::env::current_dir().unwrap()) + { + relpath + } else { + // not relative; use full path + &loc.file + }; + writeln!(formatted_data, + "└─ {}:{}", + relpath.display(), + loc.line + ).map_or_else(|err| log::error!("Failed to format RTT data - {:?}", err), |r|r); + } } } } From e384ee7e8bd37e861f36b88ffdee0a796c369ec5 Mon Sep 17 00:00:00 2001 From: JackN Date: Thu, 14 Apr 2022 17:25:14 -0400 Subject: [PATCH 28/29] Update CHANGELOG.md Co-authored-by: Yatekii --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f600d23611..e2885e3196 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,7 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated STM32H7 series yaml to support newly released chips. (#1011) - Debugger: Removed the CLI mode, in favour of `probe-rs-cli` which has richer functionality. (#1041) - Renamed `Probe::speed` to `Probe::speed_khz`. -- Debugger: Requires changes to DAP Client `launch.json` to prepare for WIP multi-core support. (#1072) +- Debugger: Changes to DAP Client `launch.json` to prepare for WIP multi-core support. (#1072) - `ram_download` example now uses clap syntax. ### Fixed From b4d07f52e8ab0aff63b979f9ab12dd5e234fbb39 Mon Sep 17 00:00:00 2001 From: JackN Date: Fri, 15 Apr 2022 10:56:33 -0400 Subject: [PATCH 29/29] PR Review responses --- debugger/src/debug_adapter/dap_adapter.rs | 94 ++++++++++--------- debugger/src/debug_adapter/dap_types.rs | 3 +- .../{debugProtocol.json => schema.json} | 0 debugger/src/debugger/configuration.rs | 40 ++++---- 4 files changed, 72 insertions(+), 65 deletions(-) rename debugger/src/debug_adapter/{debugProtocol.json => schema.json} (100%) diff --git a/debugger/src/debug_adapter/dap_adapter.rs b/debugger/src/debug_adapter/dap_adapter.rs index a0bd11d084..122347d9d6 100644 --- a/debugger/src/debug_adapter/dap_adapter.rs +++ b/debugger/src/debug_adapter/dap_adapter.rs @@ -1265,18 +1265,23 @@ impl DebugAdapter

{ // The memory address for the next read from target memory. We have to manually adjust it to be word aligned, and make sure it doesn't underflow/overflow. let mut read_pointer = if byte_offset.is_negative() { - memory_reference.saturating_sub(byte_offset.abs()) + Some(memory_reference.saturating_sub(byte_offset.abs()) as u32) } else { - memory_reference.saturating_add(byte_offset) - } as u32; + Some(memory_reference.saturating_add(byte_offset) as u32) + }; read_pointer = if instruction_offset_as_bytes.is_negative() { - read_pointer.saturating_sub(instruction_offset_as_bytes.abs() as u32) + read_pointer.map(|rp| rp.saturating_sub(instruction_offset_as_bytes.abs() as u32)) } else { - read_pointer.saturating_add(instruction_offset_as_bytes as u32) + read_pointer.map(|rp| rp.saturating_add(instruction_offset_as_bytes as u32)) }; // The memory address for the next instruction to be disassembled - let mut instruction_pointer = read_pointer as u64; + let mut instruction_pointer = if let Some(read_pointer) = read_pointer { + read_pointer as u64 + } else { + let error_message = format!("Unable to calculate starting address for disassembly request with memory reference:{:#010X}, byte offset:{:#010X}, and instruction offset:{:#010X}.", memory_reference, byte_offset, instruction_offset); + return Err(DebuggerError::Other(anyhow!(error_message))); + }; // We will only include source location data in a resulting instruction, if it is different from the previous one. let mut stored_source_location = None; @@ -1284,38 +1289,41 @@ impl DebugAdapter

{ // The MS DAP spec requires that we always have to return a fixed number of instructions. while assembly_lines.len() < instruction_count as usize { if read_more_bytes { - match target_core.core.read_word_32(read_pointer) { - Ok(new_word) => { - // Advance the read pointer for next time we need it. - read_pointer = if let Some(valid_read_pointer) = read_pointer.checked_add(4) - { - valid_read_pointer - } else { - // If this happens, the next loop will generate "invalid instruction" records. - read_pointer = u32::MAX; + if let Some(current_read_pointer) = read_pointer { + match target_core.core.read_word_32(current_read_pointer) { + Ok(new_word) => { + // Advance the read pointer for next time we need it. + read_pointer = if let Some(valid_read_pointer) = + current_read_pointer.checked_add(4) + { + Some(valid_read_pointer) + } else { + // If this happens, the next loop will generate "invalid instruction" records. + read_pointer = None; + continue; + }; + // Update the code buffer. + for new_byte in new_word.to_le_bytes() { + code_buffer.push(new_byte); + } + } + Err(_) => { + // If we can't read data at a given address, then create a "invalid instruction" record, and keep trying. + assembly_lines.push(dap_types::DisassembledInstruction { + address: format!("{:#010X}", current_read_pointer), + column: None, + end_column: None, + end_line: None, + instruction: "".to_string(), + instruction_bytes: None, + line: None, + location: None, + symbol: None, + }); + read_pointer = Some(current_read_pointer.saturating_add(4)); continue; - }; - // Update the code buffer. - for new_byte in new_word.to_le_bytes() { - code_buffer.push(new_byte); } } - Err(_) => { - // If we can't read data at a given address, then create a "invalid instruction" record, and keep trying. - read_pointer = read_pointer.saturating_add(4); - assembly_lines.push(dap_types::DisassembledInstruction { - address: format!("{:#010X}", read_pointer), - column: None, - end_column: None, - end_line: None, - instruction: "".to_string(), - instruction_bytes: None, - line: None, - location: None, - symbol: None, - }); - continue; - } } } @@ -1508,13 +1516,13 @@ impl DebugAdapter

{ }, // We use fully qualified Peripheral.Register.Field form to ensure the `evaluate` request can find the right registers and fields by name. evaluate_name: Some(variable.name.to_string()), - memory_reference: Some(format!( - "{:#010x}", - variable - .memory_location - .memory_address() - .unwrap_or(u32::MAX) - )), + memory_reference: variable + .memory_location + .memory_address() + .map_or_else( + |_| None, + |address| Some(format!("{:#010x}", address)), + ), indexed_variables: Some(indexed_child_variables_cnt), named_variables: Some(named_child_variables_cnt), presentation_hint: None, @@ -1899,7 +1907,7 @@ impl DebugAdapter

{ response_message = format!("{}\t", response_message); } response_message = format!( - "{}{:?}", + "{}{}", response_message, ::to_string(source_error) ); diff --git a/debugger/src/debug_adapter/dap_types.rs b/debugger/src/debug_adapter/dap_types.rs index 440eb89f91..f6aa2ca4f0 100644 --- a/debugger/src/debug_adapter/dap_types.rs +++ b/debugger/src/debug_adapter/dap_types.rs @@ -7,7 +7,8 @@ use schemafy::schemafy; use serde::{Deserialize, Serialize}; use std::convert::TryFrom; -schemafy!(root: debugserver_types "src/debug_adapter/debugProtocol.json"); +// Convert the MSDAP `debugAdaptor.json` file, renamed here as `schema.json` into Rust types. +schemafy!(root: debugserver_types "src/debug_adapter/schema.json"); /// Custom 'quit' request, so that VSCode can tell the `probe-rs-debugger` to terminate its own process. #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] diff --git a/debugger/src/debug_adapter/debugProtocol.json b/debugger/src/debug_adapter/schema.json similarity index 100% rename from debugger/src/debug_adapter/debugProtocol.json rename to debugger/src/debug_adapter/schema.json diff --git a/debugger/src/debugger/configuration.rs b/debugger/src/debugger/configuration.rs index 94cee71ba8..2227242ca0 100644 --- a/debugger/src/debugger/configuration.rs +++ b/debugger/src/debugger/configuration.rs @@ -54,11 +54,11 @@ impl SessionConfig { /// Ensure all file names are correctly specified and that the files they point to are accessible. pub(crate) fn validate_config_files(&mut self) -> Result<(), DebuggerError> { // Update the `cwd`. - self.cwd = self.validate_and_update_cwd()?; + self.cwd = self.resolve_cwd()?; for target_core_config in &mut self.core_configs { // Update the `program_binary` and validate that the file exists. - target_core_config.program_binary = match qualify_and_update_os_file_path( + target_core_config.program_binary = match get_absolute_path( self.cwd.clone(), target_core_config.program_binary.as_ref(), ) { @@ -79,31 +79,29 @@ impl SessionConfig { }; // Update the `svd_file` and validate that the file exists. // If there is a problem with this file, warn the user and continue with the session. - target_core_config.svd_file = match qualify_and_update_os_file_path( - self.cwd.clone(), - target_core_config.svd_file.as_ref(), - ) { - Ok(svd_file) => { - if !svd_file.is_file() { - log::error!("SVD file {:?} not found.", svd_file); + target_core_config.svd_file = + match get_absolute_path(self.cwd.clone(), target_core_config.svd_file.as_ref()) { + Ok(svd_file) => { + if !svd_file.is_file() { + log::error!("SVD file {:?} not found.", svd_file); + None + } else { + Some(svd_file) + } + } + Err(error) => { + // SVD file is not mandatory. + log::debug!("SVD file not specified: {:?}", &error); None - } else { - Some(svd_file) } - } - Err(error) => { - // SVD file is not mandatory. - log::debug!("SVD file not specified: {:?}", &error); - None - } - }; + }; } Ok(()) } - /// Validate the new cwd, or else set it from the environment. - pub(crate) fn validate_and_update_cwd(&self) -> Result, DebuggerError> { + /// Validate the new given cwd for this process exists, or else update the cwd setting to use the running process' current working directory. + pub(crate) fn resolve_cwd(&self) -> Result, DebuggerError> { Ok(match &self.cwd { Some(temp_path) => { if temp_path.is_dir() { @@ -128,7 +126,7 @@ impl SessionConfig { } /// If the path to the program to be debugged is relative, we join if with the cwd. -fn qualify_and_update_os_file_path( +fn get_absolute_path( configured_cwd: Option, os_file_to_validate: Option<&PathBuf>, ) -> Result {