diff --git a/loc/lcl/CHS/OpenFolderSchema.json.lcl b/loc/lcl/CHS/OpenFolderSchema.json.lcl index 140e54f7f..a17ab9b24 100644 --- a/loc/lcl/CHS/OpenFolderSchema.json.lcl +++ b/loc/lcl/CHS/OpenFolderSchema.json.lcl @@ -865,6 +865,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/loc/lcl/CHT/OpenFolderSchema.json.lcl b/loc/lcl/CHT/OpenFolderSchema.json.lcl index d05fe03da..0ce4d648b 100644 --- a/loc/lcl/CHT/OpenFolderSchema.json.lcl +++ b/loc/lcl/CHT/OpenFolderSchema.json.lcl @@ -865,6 +865,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/loc/lcl/CSY/OpenFolderSchema.json.lcl b/loc/lcl/CSY/OpenFolderSchema.json.lcl index e444a35ba..d3dd4ad6f 100644 --- a/loc/lcl/CSY/OpenFolderSchema.json.lcl +++ b/loc/lcl/CSY/OpenFolderSchema.json.lcl @@ -865,6 +865,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/loc/lcl/DEU/OpenFolderSchema.json.lcl b/loc/lcl/DEU/OpenFolderSchema.json.lcl index d00b8ffe8..b8941d421 100644 --- a/loc/lcl/DEU/OpenFolderSchema.json.lcl +++ b/loc/lcl/DEU/OpenFolderSchema.json.lcl @@ -865,6 +865,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/loc/lcl/ESN/OpenFolderSchema.json.lcl b/loc/lcl/ESN/OpenFolderSchema.json.lcl index 08193bf9a..8a9a7376d 100644 --- a/loc/lcl/ESN/OpenFolderSchema.json.lcl +++ b/loc/lcl/ESN/OpenFolderSchema.json.lcl @@ -814,6 +814,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/loc/lcl/JPN/OpenFolderSchema.json.lcl b/loc/lcl/JPN/OpenFolderSchema.json.lcl index 319c23253..68e3dd5f7 100644 --- a/loc/lcl/JPN/OpenFolderSchema.json.lcl +++ b/loc/lcl/JPN/OpenFolderSchema.json.lcl @@ -865,6 +865,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/loc/lcl/KOR/OpenFolderSchema.json.lcl b/loc/lcl/KOR/OpenFolderSchema.json.lcl index fcc9775b5..2aa095794 100644 --- a/loc/lcl/KOR/OpenFolderSchema.json.lcl +++ b/loc/lcl/KOR/OpenFolderSchema.json.lcl @@ -865,6 +865,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/loc/lcl/RUS/OpenFolderSchema.json.lcl b/loc/lcl/RUS/OpenFolderSchema.json.lcl index 23799a41e..03db31052 100644 --- a/loc/lcl/RUS/OpenFolderSchema.json.lcl +++ b/loc/lcl/RUS/OpenFolderSchema.json.lcl @@ -814,6 +814,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/DebugEngineHost.VSCode/HostMarshal.cs b/src/DebugEngineHost.VSCode/HostMarshal.cs index f6a243124..41f780164 100644 --- a/src/DebugEngineHost.VSCode/HostMarshal.cs +++ b/src/DebugEngineHost.VSCode/HostMarshal.cs @@ -4,6 +4,7 @@ using Microsoft.DebugEngineHost.VSCode; using Microsoft.VisualStudio.Debugger.Interop; using System; +using System.Runtime.InteropServices; namespace Microsoft.DebugEngineHost { @@ -110,7 +111,7 @@ public static IDebugFunctionPosition2 GetDebugFunctionPositionForIntPtr(IntPtr f /// public static string GetDataBreakpointStringForIntPtr(IntPtr stringId) { - throw new NotImplementedException(); + return Marshal.PtrToStringBSTR(stringId); } /// @@ -120,7 +121,7 @@ public static string GetDataBreakpointStringForIntPtr(IntPtr stringId) /// IntPtr to a BSTR which can be returned to VS. public static IntPtr GetIntPtrForDataBreakpointAddress(string address) { - throw new NotImplementedException(); + return Marshal.StringToBSTR(address); } /// diff --git a/src/DebugEngineHost.VSCode/VSCode/EngineConfiguration.cs b/src/DebugEngineHost.VSCode/VSCode/EngineConfiguration.cs index 938ed4a49..265d4246c 100644 --- a/src/DebugEngineHost.VSCode/VSCode/EngineConfiguration.cs +++ b/src/DebugEngineHost.VSCode/VSCode/EngineConfiguration.cs @@ -33,6 +33,7 @@ public sealed class EngineConfiguration private bool _conditionalBP; private bool _functionBP; private bool _clipboardContext; + private bool _dataBP; // NOTE: CoreCLR doesn't support providing a code base when loading assemblies. So all debug engines // must be placed in the directory of OpenDebugAD7.exe @@ -74,6 +75,12 @@ public bool ClipboardContext set { SetProperty(out _clipboardContext, value); } } + public bool DataBP + { + get { return _dataBP; } + set { SetProperty(out _dataBP, value); } + } + /// /// Provides the directory of the debug adapter. This is the directory where diff --git a/src/IOSDebugLauncher/Launcher.cs b/src/IOSDebugLauncher/Launcher.cs index 084a24cd3..9c5461a4c 100644 --- a/src/IOSDebugLauncher/Launcher.cs +++ b/src/IOSDebugLauncher/Launcher.cs @@ -92,7 +92,7 @@ void IPlatformAppLauncher.SetupForDebugging(out LaunchOptions debuggerLaunchOpti return _client.ServerCertificateValidationCallback(sender, (X509Certificate)certificate, (X509Chain)chain, sslPolicyErrors); }; } - + debuggerLaunchOptions.TargetArchitecture = _launchOptions.TargetArchitecture; debuggerLaunchOptions.AdditionalSOLibSearchPath = _launchOptions.AdditionalSOLibSearchPath; debuggerLaunchOptions.DebuggerMIMode = MIMode.Lldb; diff --git a/src/MICore/JsonLaunchOptions.cs b/src/MICore/JsonLaunchOptions.cs index 2b63b144c..5dc63d0bb 100644 --- a/src/MICore/JsonLaunchOptions.cs +++ b/src/MICore/JsonLaunchOptions.cs @@ -72,6 +72,12 @@ public abstract partial class BaseOptions [JsonProperty("miDebuggerServerAddress", DefaultValueHandling = DefaultValueHandling.Ignore)] public string MiDebuggerServerAddress { get; set; } + /// + /// If true, use gdb extended-remote mode to connect to gdbserver. + /// + [JsonProperty("useExtendedRemote", DefaultValueHandling = DefaultValueHandling.Ignore)] + public bool? UseExtendedRemote { get; set; } + /// /// Optional source file mappings passed to the debug engine. Example: '{ "/original/source/path":"/current/source/path" }' /// @@ -137,6 +143,7 @@ public AttachOptions( string miDebuggerPath = null, string miDebuggerArgs = null, string miDebuggerServerAddress = null, + bool? useExtendedRemote = null, HardwareBreakpointInfo hardwareBreakpointInfo = null, Dictionary sourceFileMap = null, PipeTransport pipeTransport = null, @@ -152,6 +159,7 @@ public AttachOptions( this.MiDebuggerPath = miDebuggerPath; this.MiDebuggerArgs = miDebuggerArgs; this.MiDebuggerServerAddress = miDebuggerServerAddress; + this.UseExtendedRemote = useExtendedRemote; this.ProcessId = processId; this.HardwareBreakpointInfo = hardwareBreakpointInfo; this.SourceFileMap = sourceFileMap; @@ -390,6 +398,7 @@ public LaunchOptions( string miDebuggerPath = null, string miDebuggerArgs = null, string miDebuggerServerAddress = null, + bool? useExtendedRemote = null, bool? stopAtEntry = null, string debugServerPath = null, string debugServerArgs = null, @@ -421,6 +430,7 @@ public LaunchOptions( this.MiDebuggerPath = miDebuggerPath; this.MiDebuggerArgs = miDebuggerArgs; this.MiDebuggerServerAddress = miDebuggerServerAddress; + this.UseExtendedRemote = useExtendedRemote; this.StopAtEntry = stopAtEntry; this.DebugServerPath = debugServerPath; this.DebugServerArgs = debugServerArgs; diff --git a/src/MICore/LaunchOptions.cs b/src/MICore/LaunchOptions.cs index 2315055b6..ad4740091 100644 --- a/src/MICore/LaunchOptions.cs +++ b/src/MICore/LaunchOptions.cs @@ -443,6 +443,18 @@ public LocalLaunchOptions(string MIDebuggerPath, string MIDebuggerServerAddress, this.MIDebuggerArgs = MIDebuggerArgs; } + public LocalLaunchOptions(string MIDebuggerPath, string MIDebuggerServerAddress, bool UseExtendedRemote) : + this(MIDebuggerPath, MIDebuggerServerAddress) + { + this.UseExtendedRemote = UseExtendedRemote; + } + + public LocalLaunchOptions(string MIDebuggerPath, string MIDebuggerServerAddress, string MIDebuggerArgs, bool UseExtendedRemote) : + this(MIDebuggerPath, MIDebuggerServerAddress, MIDebuggerArgs) + { + this.UseExtendedRemote = UseExtendedRemote; + } + private void InitializeServerOptions(Json.LaunchOptions.LaunchOptions launchOptions) { if (!String.IsNullOrWhiteSpace(launchOptions.DebugServerPath)) @@ -533,7 +545,8 @@ static internal LocalLaunchOptions CreateFromJson(JObject parsedOptions) LocalLaunchOptions localLaunchOptions = new LocalLaunchOptions(RequireAttribute(miDebuggerPath, nameof(miDebuggerPath)), launchOptions.MiDebuggerServerAddress, - launchOptions.MiDebuggerArgs + launchOptions.MiDebuggerArgs, + launchOptions.UseExtendedRemote.GetValueOrDefault(false) ); // Load up common options @@ -561,7 +574,8 @@ static internal LocalLaunchOptions CreateFromXml(Xml.LaunchOptions.LocalLaunchOp var options = new LocalLaunchOptions( RequireAttribute(miDebuggerPath, "MIDebuggerPath"), source.MIDebuggerServerAddress, - source.MIDebuggerArgs); + source.MIDebuggerArgs, + source.UseExtendedRemote); options.InitializeCommonOptions(source); options.InitializeServerOptions(source); options._useExternalConsole = source.ExternalConsole; @@ -666,6 +680,11 @@ private static string EnsureDebuggerPath(string miDebuggerPath, string debuggerB /// public string MIDebuggerServerAddress { get; private set; } + /// + /// [Optional] If true, use gdb extended-remote mode to connect to gdbserver. + /// + public bool UseExtendedRemote { get; private set; } + /// /// [Optional] MI Debugger Server exe, if non-null then the MIEngine will start the debug server before starting the debugger /// diff --git a/src/MICore/LaunchOptions.xsd b/src/MICore/LaunchOptions.xsd index b5ef5db1e..2e3f63b9b 100644 --- a/src/MICore/LaunchOptions.xsd +++ b/src/MICore/LaunchOptions.xsd @@ -230,6 +230,11 @@ Network address of the MI Debugger Server to connect to (example: localhost:1234). + + + If true, use gdb extended-remote mode to connect to gdbserver. + + Full path to the server executable. If non-null then the MIEngine will start the server. diff --git a/src/MICore/LaunchOptions.xsd.types.designer.cs b/src/MICore/LaunchOptions.xsd.types.designer.cs index 5551c7e82..f8b2766a2 100644 --- a/src/MICore/LaunchOptions.xsd.types.designer.cs +++ b/src/MICore/LaunchOptions.xsd.types.designer.cs @@ -575,7 +575,11 @@ public partial class LocalLaunchOptions : BaseLaunchOptions { /// [System.Xml.Serialization.XmlAttributeAttribute()] public string MIDebuggerServerAddress; - + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public bool UseExtendedRemote; + /// [System.Xml.Serialization.XmlAttributeAttribute()] public string DebugServer; diff --git a/src/MIDebugEngine/AD7.Impl/AD7PendingBreakpoint.cs b/src/MIDebugEngine/AD7.Impl/AD7PendingBreakpoint.cs index e284d76b9..3dab46a82 100644 --- a/src/MIDebugEngine/AD7.Impl/AD7PendingBreakpoint.cs +++ b/src/MIDebugEngine/AD7.Impl/AD7PendingBreakpoint.cs @@ -321,6 +321,11 @@ private int BindWithTimeout() this.SetError(new AD7ErrorBreakpoint(this, ResourceStrings.LongBind, enum_BP_ERROR_TYPE.BPET_SEV_LOW | enum_BP_ERROR_TYPE.BPET_TYPE_WARNING), true); return Constants.S_FALSE; } + else if (this._BPError != null) + { + // Ran into some sort of error + return Constants.E_FAIL; + } else { if ((enum_BP_LOCATION_TYPE)_bpRequestInfo.bpLocation.bpLocationType == enum_BP_LOCATION_TYPE.BPLT_DATA_STRING) @@ -364,7 +369,16 @@ internal async Task BindAsync() } else { - bindResult = await PendingBreakpoint.Bind(_address, _size, _engine.DebuggedProcess, _condition, this); + try + { + bindResult = await PendingBreakpoint.Bind(_address, _size, _engine.DebuggedProcess, _condition, this); + } + catch (ArgumentException ex) + { + // There was an error binding at the address with the specified size. This could happen if the size is greater + // than the max size a data bp can watch. + bindResult = new PendingBreakpoint.BindResult(ex.Message); + } } lock (_boundBreakpoints) diff --git a/src/MIDebugEngine/AD7.Impl/AD7Property.cs b/src/MIDebugEngine/AD7.Impl/AD7Property.cs index 6387aed3c..596068765 100644 --- a/src/MIDebugEngine/AD7.Impl/AD7Property.cs +++ b/src/MIDebugEngine/AD7.Impl/AD7Property.cs @@ -141,10 +141,32 @@ public int EnumChildren(enum_DEBUGPROP_INFO_FLAGS dwFields, uint dwRadix, ref Gu { _engine.DebuggedProcess.Natvis.WaitDialog.ShowWaitDialog(_variableInformation.Name); var children = _engine.DebuggedProcess.Natvis.Expand(_variableInformation); - DEBUG_PROPERTY_INFO[] properties = new DEBUG_PROPERTY_INFO[children.Length]; - for (int i = 0; i < children.Length; i++) + + // Count number of children that fit filter (results saved in "fitsFilter") + int propertyCount = children.Length; + bool[] fitsFilter = null; + if (!string.IsNullOrEmpty(pszNameFilter)) + { + fitsFilter = new bool[children.Length]; + for (int i = 0; i < children.Length; i++) + { + fitsFilter[i] = string.Equals(children[i].Name, pszNameFilter, StringComparison.Ordinal); + if (!fitsFilter[i]) + { + propertyCount--; + } + } + } + + // Create property array + DEBUG_PROPERTY_INFO[] properties = new DEBUG_PROPERTY_INFO[propertyCount]; + for (int i = 0, j = 0; i < children.Length; i++) { - properties[i] = (new AD7Property(_engine, children[i])).ConstructDebugPropertyInfo(dwFields); + if (fitsFilter == null || fitsFilter[i]) + { + properties[j] = (new AD7Property(_engine, children[i])).ConstructDebugPropertyInfo(dwFields); + ++j; // increment j if we fit filter, this allows us to traverse "properties" array properly. + } } ppEnum = new AD7PropertyEnum(properties); return Constants.S_OK; diff --git a/src/MIDebugEngine/Engine.Impl/CygwinFileMapper.cs b/src/MIDebugEngine/Engine.Impl/CygwinFileMapper.cs index 534a6ef5a..a8339fb8d 100644 --- a/src/MIDebugEngine/Engine.Impl/CygwinFileMapper.cs +++ b/src/MIDebugEngine/Engine.Impl/CygwinFileMapper.cs @@ -22,6 +22,14 @@ public CygwinFilePathMapper(DebuggedProcess debuggedProcess) _cygwinToWindows = new Dictionary(); } + /// + /// Maps cygwin paths (/usr/bin) to Windows Paths (C:\User\bin) + /// + /// We cache these paths because most of the time it comes from callstacks that we need to provide + /// source file maps. + /// + /// A string representing the cygwin path. + /// A string as a full Windows path public string MapCygwinToWindows(string origCygwinPath) { if (!(_debuggedProcess.LaunchOptions is LocalLaunchOptions)) @@ -39,7 +47,7 @@ public string MapCygwinToWindows(string origCygwinPath) { if (!_cygwinToWindows.TryGetValue(cygwinPath, out windowsPath)) { - if (!LaunchCygPathAndReadResult(cygwinPath, localLaunchOptions.MIDebuggerPath, out windowsPath)) + if (!LaunchCygPathAndReadResult(cygwinPath, localLaunchOptions.MIDebuggerPath, convertToWindowsPath: true, out windowsPath)) { return origCygwinPath; } @@ -51,14 +59,40 @@ public string MapCygwinToWindows(string origCygwinPath) return windowsPath; } + /// + /// Maps Windows paths (C:\User\bin) to Cygwin Paths (/usr/bin) + /// + /// Not cached since we only do this for setting the program, symbol, and working dir. + /// + /// A string representing the Windows path. + /// A string as a full unix path + public string MapWindowsToCygwin(string origWindowsPath) + { + if (!(_debuggedProcess.LaunchOptions is LocalLaunchOptions)) + { + return origWindowsPath; + } + + LocalLaunchOptions localLaunchOptions = (LocalLaunchOptions)_debuggedProcess.LaunchOptions; + + string windowsPath = PlatformUtilities.UnixPathToWindowsPath(origWindowsPath); + + if (!LaunchCygPathAndReadResult(windowsPath, localLaunchOptions.MIDebuggerPath, convertToWindowsPath: false, out string cygwinPath)) + { + return origWindowsPath; + } + + return cygwinPath; + } + // There is an issue launching Cygwin apps that if a process is launched using a bitness mismatched console, // process launch will fail. To avoid that, launch cygpath with its own console. This requires calling CreateProcess // directly because the creation flags are not exposed in System.Diagnostics.Process. // // Return true if successful. False otherwise. - private bool LaunchCygPathAndReadResult(string cygwinPath, string miDebuggerPath, out string windowsPath) + private bool LaunchCygPathAndReadResult(string inputPath, string miDebuggerPath, bool convertToWindowsPath, out string outputPath) { - windowsPath = ""; + outputPath = ""; if (String.IsNullOrEmpty(miDebuggerPath)) { @@ -108,8 +142,19 @@ private bool LaunchCygPathAndReadResult(string cygwinPath, string miDebuggerPath const uint DETACHED_PROCESS = 0x00000008; uint flags = DETACHED_PROCESS; - // ex: "C:\\cygwin64\\bin\\cygpath.exe -w " + cygwinPath, - string command = String.Concat(cygpathPath, " -w ", cygwinPath); + string command = string.Empty; + if (convertToWindowsPath) + { + // -w, --windows print Windows form of NAMEs (C:\WINNT) + // ex: "C:\\cygwin64\\bin\\cygpath.exe -w " + inputPath, + command = String.Concat(cygpathPath, " -w ", inputPath); + } + else + { + // -u, --unix (default) print Unix form of NAMEs (/cygdrive/c/winnt) + // ex: "C:\\cygwin64\\bin\\cygpath.exe -u " + inputPath, + command = String.Concat(cygpathPath, " -u ", inputPath); + } if (!CreateProcess( null, command, @@ -153,7 +198,7 @@ out processInfo FileStream fs = new FileStream(stdoutRead, FileAccess.Read); StreamReader sr = new StreamReader(fs); - windowsPath = sr.ReadLine(); + outputPath = sr.ReadLine(); } finally { diff --git a/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs b/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs index e4f655971..5375744d9 100755 --- a/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs +++ b/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs @@ -551,6 +551,9 @@ public async Task Initialize(HostWaitLoop waitLoop, CancellationToken token) try { await this.MICommandFactory.EnableTargetAsyncOption(); + + await this.CheckCygwin(_launchOptions as LocalLaunchOptions); + List commands = await GetInitializeCommands(); _childProcessHandler?.Enable(); @@ -632,9 +635,11 @@ private async Task> GetInitializeCommands() commands.Add(new LaunchCommand("-gdb-set solib-absolute-prefix " + _launchOptions.AbsolutePrefixSOLibSearchPath)); } - // On Windows ';' appears to correctly works as a path seperator and from the documentation, it is ':' on unix - string pathEntrySeperator = _launchOptions.UseUnixSymbolPaths ? ":" : ";"; - string escapedSearchPath = string.Join(pathEntrySeperator, _launchOptions.GetSOLibSearchPath().Select(path => EscapeSymbolPath(path, ignoreSpaces: true))); + // On Windows ';' appears to correctly works as a path seperator and from the documentation, it is ':' on unix or cygwin envrionments + string pathEntrySeperator = (_launchOptions.UseUnixSymbolPaths || IsCygwin) ? ":" : ";"; + string escapedSearchPath = string.Join(pathEntrySeperator, _launchOptions.GetSOLibSearchPath().Select(path => { + return EnsureProperPathSeparators(path, ignoreSpaces: true); + })); if (!string.IsNullOrWhiteSpace(escapedSearchPath)) { if (_launchOptions.DebuggerMIMode == MIMode.Gdb) @@ -691,7 +696,7 @@ private async Task> GetInitializeCommands() this.AddGetTargetArchitectureCommand(commands); // Add core dump information (linux/mac does not support quotes around this path but spaces in the path do work) - string coreDump = this.UseUnixPathSeparators ? _launchOptions.CoreDumpPath : this.EnsureProperPathSeparators(_launchOptions.CoreDumpPath); + string coreDump = this.UseUnixPathSeparators ? _launchOptions.CoreDumpPath : this.EnsureProperPathSeparators(_launchOptions.CoreDumpPath, true); string coreDumpCommand = _launchOptions.DebuggerMIMode == MIMode.Lldb ? String.Concat("target create --core ", coreDump) : String.Concat("-target-select core ", coreDump); string coreDumpDescription = String.Format(CultureInfo.CurrentCulture, ResourceStrings.LoadingCoreDumpMessage, _launchOptions.CoreDumpPath); commands.Add(new LaunchCommand(coreDumpCommand, coreDumpDescription, ignoreFailures: false)); @@ -700,8 +705,6 @@ private async Task> GetInitializeCommands() { // This is an attach - CheckCygwin(commands, localLaunchOptions); - if (this.MICommandFactory.Mode == MIMode.Gdb) { if (_launchOptions is UnixShellPortLaunchOptions) @@ -723,11 +726,14 @@ private async Task> GetInitializeCommands() // check for remote string destination = localLaunchOptions?.MIDebuggerServerAddress; + bool useExtendedRemote = localLaunchOptions?.UseExtendedRemote ?? false; if (!string.IsNullOrWhiteSpace(destination)) { - commands.Add(new LaunchCommand("-target-select remote " + destination, string.Format(CultureInfo.CurrentCulture, ResourceStrings.ConnectingMessage, destination))); + string remoteMode = useExtendedRemote ? "extended-remote" : "remote"; + commands.Add(new LaunchCommand($"-target-select {remoteMode} {destination}", string.Format(CultureInfo.CurrentCulture, ResourceStrings.ConnectingMessage, destination))); } - else // gdbserver is already attached when using LocalLaunchOptions + // Allow attach after connection only in extended-remote mode + if (useExtendedRemote || (!useExtendedRemote && string.IsNullOrWhiteSpace(destination))) { Action failureHandler = (string miError) => { @@ -766,7 +772,8 @@ private async Task> GetInitializeCommands() if (!string.IsNullOrWhiteSpace(_launchOptions.WorkingDirectory)) { - string escapedDir = this.EnsureProperPathSeparators(_launchOptions.WorkingDirectory); + string escapedDir = this.EnsureProperPathSeparators(_launchOptions.WorkingDirectory, true); + commands.Add(new LaunchCommand("-environment-cd " + escapedDir)); } @@ -779,8 +786,6 @@ private async Task> GetInitializeCommands() commands.Add(new LaunchCommand("-gdb-set new-console on", ignoreFailures: true)); } - CheckCygwin(commands, localLaunchOptions); - this.AddExecutablePathCommand(commands); // Important: this must occur after file-exec-and-symbols but before anything else. @@ -829,7 +834,8 @@ private async Task> GetInitializeCommands() string destination = localLaunchOptions.MIDebuggerServerAddress; if (!string.IsNullOrWhiteSpace(destination)) { - commands.Add(new LaunchCommand("-target-select remote " + destination, string.Format(CultureInfo.CurrentCulture, ResourceStrings.ConnectingMessage, destination))); + string remoteMode = localLaunchOptions.UseExtendedRemote ? "extended-remote" : "remote"; + commands.Add(new LaunchCommand($"-target-select {remoteMode} {destination}", string.Format(CultureInfo.CurrentCulture, ResourceStrings.ConnectingMessage, destination))); if (localLaunchOptions.RequireHardwareBreakpoints && localLaunchOptions.HardwareBreakpointLimit > 0) { commands.Add(new LaunchCommand(string.Format(CultureInfo.InvariantCulture, "-interpreter-exec console \"set remote hardware-breakpoint-limit {0}\"", localLaunchOptions.HardwareBreakpointLimit.ToString(CultureInfo.InvariantCulture)))); } @@ -853,40 +859,54 @@ private async Task> GetInitializeCommands() return commands; } - private void CheckCygwin(List commands, LocalLaunchOptions localLaunchOptions) + /// + /// Checks to see if we are running Cygwin or not. + /// + /// + /// + private async Task CheckCygwin(LocalLaunchOptions localLaunchOptions) { - // If running locally on windows, determine if gdb is running from cygwin - if (localLaunchOptions != null && PlatformUtilities.IsWindows() && this.MICommandFactory.Mode == MIMode.Gdb) - { + // Checks to see if: + // 1. LocalLaunch Debugging + // 2. On Windows + // 3. With GDB + // 4. Does not have custom commands + // 5. Is not Android Debugging + // 6. Is not Dump Debugging + if (localLaunchOptions != null && + PlatformUtilities.IsWindows() && + this.MICommandFactory.Mode == MIMode.Gdb && + localLaunchOptions.CustomLaunchSetupCommands == null && + localLaunchOptions.DeviceAppLauncher == null && + !this.IsCoreDump) + { + string resultString = await ConsoleCmdAsync("show configuration", allowWhileRunning: false, ignoreFailures: true); + // mingw will not implement this command, but to be safe, also check if the results contains the string cygwin. - LaunchCommand lc = new LaunchCommand("show configuration", null, true, null, (string resStr) => - { - // Look to see if configuration has "cywgin" within a word boundry. - // Also look for "msys" since it is a modified version of Cygwin. - if (Regex.IsMatch(resStr, "\\bcygwin\\b|\\bmsys\\b")) - { - this.IsCygwin = true; - this.CygwinFilePathMapper = new CygwinFilePathMapper(this); - _engineTelemetry.SendWindowsRuntimeEnvironment(EngineTelemetry.WindowsRuntimeEnvironment.Cygwin); - } - else - { - this.IsMinGW = true; - // Gdb on windows and not cygwin implies mingw - _engineTelemetry.SendWindowsRuntimeEnvironment(EngineTelemetry.WindowsRuntimeEnvironment.MinGW); - } + // Look to see if configuration has "cywgin" within a word boundry. + // Also look for "msys" since it is a modified version of Cygwin. + if (Regex.IsMatch(resultString, "\\bcygwin\\b|\\bmsys\\b")) + { + this.IsCygwin = true; + this.CygwinFilePathMapper = new CygwinFilePathMapper(this); - return Task.FromResult(0); - }); - commands.Add(lc); + _engineTelemetry.SendWindowsRuntimeEnvironment(EngineTelemetry.WindowsRuntimeEnvironment.Cygwin); + } + else + { + this.IsMinGW = true; + // Gdb on windows and not cygwin implies mingw + _engineTelemetry.SendWindowsRuntimeEnvironment(EngineTelemetry.WindowsRuntimeEnvironment.MinGW); + } } } private void AddExecutablePathCommand(IList commands) { - string exe = this.EnsureProperPathSeparators(_launchOptions.ExePath); - string description = string.Format(CultureInfo.CurrentCulture, ResourceStrings.LoadingSymbolMessage, _launchOptions.ExePath); + string exe = this.EnsureProperPathSeparators(_launchOptions.ExePath, true); + + string description = string.Format(CultureInfo.CurrentCulture, ResourceStrings.LoadingSymbolMessage, exe); Action failureHandler = (string miError) => { @@ -1424,36 +1444,22 @@ internal WorkerThread WorkerThread get { return _worker; } } + private readonly char[] RemotePathSeperators = new char[] { ' ', '\'' }; + private readonly char[] LocalPathSeperators = new char[] { ' ' }; + /// - /// Use to ensure path separators are correct for files that exist on the target debugger's machine. - /// If you are debugging on Windows to a remote instance of gdb or gdbserver, it will update it to Unix path separators. + /// Use to ensure path separators are correct for files we are setting for GDB. + /// If you are debugging on Windows to a remote instance of gdb or gdbserver, it will update it to Unix path separators that exist on the target debugger's machine. + /// If you are debugging on Windows locally, it will escape the Windows path seperator. + /// If you are debugging on Windows locally with Cygwin, we will update it to use unix path seperators and resolve the cygwin path. /// - internal string EnsureProperPathSeparators(string path) + internal string EnsureProperPathSeparators(string path, bool isRemote = false, bool ignoreSpaces = false) { - if (this.UseUnixPathSeparators) - { - path = PlatformUtilities.WindowsPathToUnixPath(path); - } - else - { - path = path.Trim(); - path = path.Replace(@"\", @"\\"); - } - - if (path.IndexOfAny(new char[] { ' ', '\'' }) != -1) + if (IsCygwin) { - path = '"' + path + '"'; + path = CygwinFilePathMapper.MapWindowsToCygwin(path); } - return path; - } - - /// - /// This method should be used to escape paths that are used by GDB (and NOT gdbserver) locally. - /// Any path that gdbserver would use in remote server scenarios should use EnsureProperPathSeparators instead. - /// - internal string EscapeSymbolPath(string path, bool ignoreSpaces = false) - { - if (this.UseUnixSymbolPaths) + else if (this.UseUnixPathSeparators) { path = PlatformUtilities.WindowsPathToUnixPath(path); } @@ -1463,7 +1469,9 @@ internal string EscapeSymbolPath(string path, bool ignoreSpaces = false) path = path.Replace(@"\", @"\\"); } - if (!ignoreSpaces && path.IndexOf(' ') != -1) + char[] pathSeperator = isRemote ? RemotePathSeperators : LocalPathSeperators; + + if (!ignoreSpaces && path.IndexOfAny(pathSeperator) != -1) { path = '"' + path + '"'; } @@ -2244,7 +2252,7 @@ public bool MapCurrentSrcToCompileTimeSrc(string currentSrc, out string compiler continue; // match didn't end at a directory separator, not actually a match } compilerSrc = Path.Combine(e.CompileTimePath, file); // map to the compiled location - if (compilerSrc.IndexOf('\\') > 0) + if (compilerSrc.IndexOf('\\') != -1) { compilerSrc = PlatformUtilities.WindowsPathToUnixPath(compilerSrc); // use Unix notation for the compiled path } diff --git a/src/MIDebugEngine/Engine.Impl/Disassembly.cs b/src/MIDebugEngine/Engine.Impl/Disassembly.cs index eb2d07993..f08b928ec 100644 --- a/src/MIDebugEngine/Engine.Impl/Disassembly.cs +++ b/src/MIDebugEngine/Engine.Impl/Disassembly.cs @@ -323,7 +323,7 @@ internal async Task> Disassemble(DebuggedProcess { if (file.IndexOf(' ') >= 0) // only needs escaping if filename contains a space { - file = process.EscapeSymbolPath(file); + file = process.EnsureProperPathSeparators(file); } string cmd = "-data-disassemble -f " + file + " -l " + line.ToString(CultureInfo.InvariantCulture) + " -n " + dwInstructions.ToString(CultureInfo.InvariantCulture) + " -- 1"; Results results = await process.CmdAsync(cmd, ResultClass.None); diff --git a/src/MIDebugEngine/Engine.Impl/SourceLine.cs b/src/MIDebugEngine/Engine.Impl/SourceLine.cs index 37b439018..f05464a68 100644 --- a/src/MIDebugEngine/Engine.Impl/SourceLine.cs +++ b/src/MIDebugEngine/Engine.Impl/SourceLine.cs @@ -92,7 +92,7 @@ internal async Task GetLinesForFile(string file) } private async Task LinesForFile(string file) { - string cmd = "-symbol-list-lines " + _process.EscapeSymbolPath(file); + string cmd = "-symbol-list-lines " + _process.EnsureProperPathSeparators(file); Results results = await _process.CmdAsync(cmd, ResultClass.None); if (results.ResultClass != ResultClass.done) diff --git a/src/MIDebugPackage/OpenFolderSchema.json b/src/MIDebugPackage/OpenFolderSchema.json index 4d69d7b0c..c7c60efec 100644 --- a/src/MIDebugPackage/OpenFolderSchema.json +++ b/src/MIDebugPackage/OpenFolderSchema.json @@ -113,6 +113,10 @@ "type": "string", "description": "Network address of the MI-enabled debugger server to connect to. \nExample: localhost:1234." }, + "useExtendedRemote": { + "type": "boolean", + "description": "If true, use gdb extended-remote mode to connect to gdbserver." + }, "setupCommands": { "type": "array", "description": "One or more GDB/LLDB commands to execute in order to setup the underlying debugger. \nExample: \"setupCommands\": [ { \"text\": \"-enable-pretty-printing\", \"description\": \"Enable GDB pretty printing\", \"ignoreFailures\": true }].", diff --git a/src/OpenDebugAD7/AD7DebugSession.cs b/src/OpenDebugAD7/AD7DebugSession.cs index 0c145ad68..bb46637d6 100644 --- a/src/OpenDebugAD7/AD7DebugSession.cs +++ b/src/OpenDebugAD7/AD7DebugSession.cs @@ -51,6 +51,8 @@ internal sealed class AD7DebugSession : DebugAdapterBase, IDebugPortNotify2, IDe private Dictionary m_functionBreakpoints; private Dictionary m_instructionBreakpoints; + private Dictionary m_dataBreakpoints; + private List m_exceptionBreakpoints; private readonly HandleCollection m_frameHandles; @@ -122,6 +124,7 @@ public AD7DebugSession(Stream debugAdapterStdIn, Stream debugAdapterStdOut, List m_breakpoints = new Dictionary>(); m_functionBreakpoints = new Dictionary(); m_instructionBreakpoints = new Dictionary(); + m_dataBreakpoints = new Dictionary(); m_exceptionBreakpoints = new List(); m_variableManager = new VariableManager(); } @@ -649,6 +652,7 @@ private VariablesResponse VariablesFromFrame(VariableScope vref, uint radix) while (varEnum.Next(1, props, out nProps) == HRConstants.S_OK) { response.Variables.Add(m_variableManager.CreateVariable(props[0].pProperty, GetDefaultPropertyInfoFlags())); + m_variableManager.AddFrameVariable(frame, props[0]); } } @@ -905,6 +909,7 @@ protected override void HandleInitializeRequestAsync(IRequestResponder responder) + { + if (responder.Arguments.Name == null) + { + responder.SetError(new ProtocolException("DataBreakpointInfo failed: Missing 'Name'.")); + return; + } + + DataBreakpointInfoResponse response = new DataBreakpointInfoResponse(); + + try + { + string name = responder.Arguments.Name; + IDebugProperty2 property = null; + string errorMessage = null; + ErrorBuilder eb = new ErrorBuilder(() => AD7Resources.Error_DataBreakpointInfoFail); + int hr = HRConstants.S_OK; + + // Did our request come with a parent object? + if (responder.Arguments.VariablesReference.HasValue) + { + int variableReference = responder.Arguments.VariablesReference.Value; + if (!m_variableManager.TryGet(variableReference, out object variableObj)) + { + responder.SetError(new ProtocolException("DataBreakpointInfo failed: Invalid 'VariableReference'.")); + return; + } + + if (variableObj is VariableScope varScope) + { + // We have a scope object. We can grab a frame for evaluation from this + IDebugStackFrame2 frame = varScope.StackFrame; + m_variableManager.TryGetProperty((frame, name), out property); + } + else if (variableObj is VariableEvaluationData varEvalData) + { + // We have a variable parent object. + IDebugProperty2 parentProperty = varEvalData.DebugProperty; + m_variableManager.TryGetProperty((variableReference, name), out property); + } + } + else + { + // We don't have a parent object. Default to using top stack frame + if (m_frameHandles == null || !m_frameHandles.TryGetFirst(out IDebugStackFrame2 frame)) + { + response.Description = string.Format(CultureInfo.CurrentCulture, AD7Resources.Error_DataBreakpointInfoFail, AD7Resources.Error_NoParentObject); + } + else + { + m_variableManager.TryGetProperty((frame, name), out property); + } + } + + // If we've found a valid child property to set the data breakpoint on, get the address/size and return the DataId. + if (property != null && property is IDebugProperty160 property160) + { + hr = property160.GetDataBreakpointInfo160(out string address, out uint size, out string displayName, out errorMessage); + eb.CheckHR(hr); + if (!string.IsNullOrEmpty(errorMessage)) + { + response.Description = string.Format(CultureInfo.CurrentCulture, AD7Resources.Error_DataBreakpointInfoFail, errorMessage); + } + else + { + // If we succeeded, return response that we can set a data bp. + string sSize = size.ToString(CultureInfo.InvariantCulture); + response.DataId = $"{address},{sSize}"; + response.Description = string.Format(CultureInfo.CurrentCulture, AD7Resources.DataBreakpointDisplayString, displayName, sSize); + response.AccessTypes = new List() { DataBreakpointAccessType.Write }; + } + } + else if (response.Description == null) + { + response.Description = string.Format(CultureInfo.CurrentCulture, AD7Resources.Error_DataBreakpointInfoFail, AD7Resources.Error_ChildPropertyNotFound); + } + } + catch (Exception ex) + { + if (ex is AD7Exception ad7ex) + response.Description = ad7ex.Message; + else + response.Description = string.Format(CultureInfo.CurrentCulture, AD7Resources.Error_DataBreakpointInfoFail, ""); + } + finally + { + responder.SetResponse(response); + } + } + + protected override void HandleSetDataBreakpointsRequestAsync(IRequestResponder responder) + { + if (responder.Arguments.Breakpoints == null) + { + responder.SetError(new ProtocolException("SetDataBreakpointRequest failed: Missing 'breakpoints'.")); + return; + } + + List breakpoints = responder.Arguments.Breakpoints; + SetDataBreakpointsResponse response = new SetDataBreakpointsResponse(); + Dictionary newBreakpoints = new Dictionary(); + ErrorBuilder eb = new ErrorBuilder(() => AD7Resources.Error_DataBreakpointInfoFail); + + try + { + foreach (KeyValuePair b in m_dataBreakpoints) + { + if (breakpoints.Find((p) => p.DataId == b.Key) != null) + { + newBreakpoints[b.Key] = b.Value; // breakpoint still in new list + } + else + { + b.Value.Delete(); // not in new list so delete it + } + } + + foreach (DataBreakpoint b in breakpoints) + { + if (m_dataBreakpoints.ContainsKey(b.DataId)) + { // already created + IDebugBreakpointRequest2 breakpointRequest; + if (m_dataBreakpoints[b.DataId].GetBreakpointRequest(out breakpointRequest) == 0 && + breakpointRequest is AD7BreakPointRequest ad7BPRequest) + { + // Check to see if this breakpoint has a condition that has changed. + if (!StringComparer.Ordinal.Equals(ad7BPRequest.Condition, b.Condition)) + { + // Condition has been modified. Delete breakpoint so it will be recreated with the updated condition. + var toRemove = m_dataBreakpoints[b.DataId]; + toRemove.Delete(); + m_dataBreakpoints.Remove(b.DataId); + } + else + { + if (ad7BPRequest.BindResult != null) + { + response.Breakpoints.Add(ad7BPRequest.BindResult); + } + else + { + response.Breakpoints.Add(new Breakpoint() + { + Id = (int)ad7BPRequest.Id, + Verified = false, + Line = 0 + }); + + } + continue; + } + } + } + + // Bind the new data bp + if (!m_dataBreakpoints.ContainsKey(b.DataId)) + { + int hr = HRConstants.S_OK; + + int lastCommaIdx = b.DataId.LastIndexOf(','); + if (lastCommaIdx == -1) + { + eb.ThrowHR(HRConstants.E_FAIL); + } + + // format is "{dataId},{size}" where dataId = "{address},{displayName}" + string strSize = b.DataId.Substring(lastCommaIdx + 1); + string address = b.DataId.Substring(0, lastCommaIdx); + uint size = uint.Parse(strSize, CultureInfo.InvariantCulture); + + AD7BreakPointRequest pBPRequest = new AD7BreakPointRequest(address, size); + hr = m_engine.CreatePendingBreakpoint(pBPRequest, out IDebugPendingBreakpoint2 pendingBp); + eb.CheckHR(hr); + + hr = pendingBp.Bind(); + if (hr == HRConstants.S_OK) + { + newBreakpoints[b.DataId] = pendingBp; + } + response.Breakpoints.Add(pBPRequest.BindResult); + } + } + responder.SetResponse(response); + } + catch (Exception ex) + { + responder.SetError(new ProtocolException(ex.Message)); + } + finally + { + m_dataBreakpoints = newBreakpoints; + } + } + + protected override void HandleSetExceptionBreakpointsRequestAsync(IRequestResponder responder) { HashSet activeExceptionCategories = new HashSet(); @@ -3310,7 +3512,7 @@ public void HandleIDebugBreakpointErrorEvent2(IDebugEngine2 pEngine, IDebugProce }; } } - else + else if (ad7BPRequest.FunctionPosition != null) { bp = new Breakpoint() { @@ -3324,6 +3526,18 @@ public void HandleIDebugBreakpointErrorEvent2(IDebugEngine2 pEngine, IDebugProce string outputMsg = string.Format(CultureInfo.CurrentCulture, AD7Resources.Error_FunctionBreakpoint, ad7BPRequest.FunctionPosition.Name, errorMsg); m_logger.WriteLine(LoggingCategory.DebuggerError, outputMsg); } + else // data bp + { + string outputMsg = string.Format(CultureInfo.CurrentCulture, AD7Resources.Error_InvalidDataBreakpoint, errorMsg); + bp = new Breakpoint() + { + Verified = false, + Id = (int)ad7BPRequest.Id, + Line = 0, + Message = outputMsg + }; + m_logger.WriteLine(LoggingCategory.DebuggerError, outputMsg); + } ad7BPRequest.BindResult = bp; Protocol.SendEvent(new BreakpointEvent(BreakpointEvent.ReasonValue.Changed, bp)); diff --git a/src/OpenDebugAD7/AD7Impl/AD7BreakPointRequest.cs b/src/OpenDebugAD7/AD7Impl/AD7BreakPointRequest.cs index 63f70afa0..2843beb40 100644 --- a/src/OpenDebugAD7/AD7Impl/AD7BreakPointRequest.cs +++ b/src/OpenDebugAD7/AD7Impl/AD7BreakPointRequest.cs @@ -25,13 +25,16 @@ static public uint GetNextBreakpointId() public IDebugMemoryContext2 MemoryContext { get; private set; } + public string DataAddress { get; private set; } + public uint DataSize { get; private set; } + // Used for Releasing the MemoryContext. // Caller of AD7BreakPointRequest(MemoryContext) is required to // release it with HostMarshal.ReleaseCodeContextId public IntPtr MemoryContextIntPtr { get; private set; } // Unique identifier for breakpoint when communicating with VSCode - public uint Id { get; private set; } + public uint Id { get; } = GetNextBreakpointId(); // Bind result from IDebugBreakpointErrorEvent2 or IDebugBreakpointBoundEvent2 public Breakpoint BindResult { get; set; } @@ -40,7 +43,6 @@ public AD7BreakPointRequest(SessionConfiguration config, string path, int line, { DocumentPosition = new AD7DocumentPosition(config, path, line); Condition = condition; - Id = GetNextBreakpointId(); } public AD7BreakPointRequest(string functionName) @@ -48,6 +50,13 @@ public AD7BreakPointRequest(string functionName) FunctionPosition = new AD7FunctionPosition(functionName); } + // Data breakpoint constructor + public AD7BreakPointRequest(string address, uint size) + { + DataAddress = address; + DataSize = size; + } + public AD7BreakPointRequest(IDebugMemoryContext2 memoryContext) { MemoryContext = memoryContext; @@ -67,7 +76,10 @@ public int GetLocationType(enum_BP_LOCATION_TYPE[] pBPLocationType) { pBPLocationType[0] = enum_BP_LOCATION_TYPE.BPLT_CODE_CONTEXT; } - + else if (DataAddress != null) + { + pBPLocationType[0] = enum_BP_LOCATION_TYPE.BPLT_DATA_STRING; + } return 0; } @@ -92,7 +104,12 @@ public int GetRequestInfo(enum_BPREQI_FIELDS dwFields, BP_REQUEST_INFO[] pBPRequ pBPRequestInfo[0].bpLocation.bpLocationType = (uint)enum_BP_LOCATION_TYPE.BPLT_CODE_CONTEXT; MemoryContextIntPtr = HostMarshal.RegisterCodeContext(MemoryContext as IDebugCodeContext2); pBPRequestInfo[0].bpLocation.unionmember1 = MemoryContextIntPtr; - + } + else if (DataAddress != null) + { + pBPRequestInfo[0].bpLocation.bpLocationType = (uint)enum_BP_LOCATION_TYPE.BPLT_DATA_STRING; + pBPRequestInfo[0].bpLocation.unionmember3 = HostMarshal.GetIntPtrForDataBreakpointAddress(DataAddress); + pBPRequestInfo[0].bpLocation.unionmember4 = (IntPtr)DataSize; } } if ((dwFields & enum_BPREQI_FIELDS.BPREQI_CONDITION) != 0 && !string.IsNullOrWhiteSpace(Condition)) diff --git a/src/OpenDebugAD7/AD7Resources.Designer.cs b/src/OpenDebugAD7/AD7Resources.Designer.cs index 6ef7590b3..516b91680 100644 --- a/src/OpenDebugAD7/AD7Resources.Designer.cs +++ b/src/OpenDebugAD7/AD7Resources.Designer.cs @@ -60,6 +60,15 @@ internal AD7Resources() { } } + /// + /// Looks up a localized string similar to When '{0}' changes ({1} bytes). + /// + internal static string DataBreakpointDisplayString { + get { + return ResourceManager.GetString("DataBreakpointDisplayString", resourceCulture); + } + } + /// /// Looks up a localized string similar to Execute debugger commands using "-exec <command>", for example "-exec info registers" will list registers in use (when GDB is the debugger). /// @@ -78,6 +87,15 @@ internal static string DebuggerDisconnectMessage { } } + /// + /// Looks up a localized string similar to Unable to find child property.. + /// + internal static string Error_ChildPropertyNotFound { + get { + return ResourceManager.GetString("Error_ChildPropertyNotFound", resourceCulture); + } + } + /// /// Looks up a localized string similar to Condition "{0}" : {1}. /// @@ -107,6 +125,15 @@ internal static string Error_CorruptingException { } } + /// + /// Looks up a localized string similar to Unable to get info for data breakpoint. {0}. + /// + internal static string Error_DataBreakpointInfoFail { + get { + return ResourceManager.GetString("Error_DataBreakpointInfoFail", resourceCulture); + } + } + /// /// Looks up a localized string similar to Exception occurred: '{0}'. /// @@ -215,6 +242,15 @@ internal static string Error_Invalid_Exception_Condition { } } + /// + /// Looks up a localized string similar to Unable to set data breakpoint: {0}. + /// + internal static string Error_InvalidDataBreakpoint { + get { + return ResourceManager.GetString("Error_InvalidDataBreakpoint", resourceCulture); + } + } + /// /// Looks up a localized string similar to DNX runtime process exited unexpectedly with error code {0}.. /// @@ -244,6 +280,15 @@ internal static string Error_MissingOutParam { } } + /// + /// Looks up a localized string similar to Unable to get parent object: No scope or variable found.. + /// + internal static string Error_NoParentObject { + get { + return ResourceManager.GetString("Error_NoParentObject", resourceCulture); + } + } + /// /// Looks up a localized string similar to Set next statement is not supported by the current debugger.. /// diff --git a/src/OpenDebugAD7/AD7Resources.resx b/src/OpenDebugAD7/AD7Resources.resx index f2f020d8a..443ed33ee 100644 --- a/src/OpenDebugAD7/AD7Resources.resx +++ b/src/OpenDebugAD7/AD7Resources.resx @@ -312,4 +312,23 @@ ERROR: '{0}' is an invalid exception condition. See https://aka.ms/VSCode-Cpp-ExceptionSettings for more information. {0} is the exception condition + + Unable to get info for data breakpoint. {0} + {0} is the reason (could be exception condition) + + + Unable to get parent object: No scope or variable found. + {0} is a number, {1} is the property name + + + When '{0}' changes ({1} bytes) + {0} is the variable name, {1} is a number (size of the variable) + + + Unable to find child property. + + + Unable to set data breakpoint: {0} + {0} is the reason + \ No newline at end of file diff --git a/src/OpenDebugAD7/VariableManager.cs b/src/OpenDebugAD7/VariableManager.cs index def25f74d..8f7d37e53 100644 --- a/src/OpenDebugAD7/VariableManager.cs +++ b/src/OpenDebugAD7/VariableManager.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Collections.Generic; using Microsoft.DebugEngineHost.VSCode; using Microsoft.VisualStudio.Debugger.Interop; using Microsoft.VisualStudio.Shared.VSCodeDebugProtocol.Messages; @@ -31,14 +32,24 @@ internal class VariableManager // NOTE: The value being stored can be a VariableScope or a VariableEvaluationData private readonly HandleCollection m_variableHandles; + // NOTE: (VariableReference, ChildName) -> IDebugProperty2 + private readonly Dictionary, IDebugProperty2> m_variablesChildren; + + // NOTE: (Frame, Name) -> IDebugProperty2 + private readonly Dictionary, IDebugProperty2> m_framesVariables; + internal VariableManager() { m_variableHandles = new HandleCollection(); + m_variablesChildren = new Dictionary, IDebugProperty2>(); + m_framesVariables = new Dictionary, IDebugProperty2>(); } internal void Reset() { m_variableHandles.Reset(); + m_variablesChildren.Clear(); + m_framesVariables.Clear(); } internal Boolean IsEmpty() @@ -46,6 +57,16 @@ internal Boolean IsEmpty() return m_variableHandles.IsEmpty; } + internal bool TryGetProperty((int, string) key, out IDebugProperty2 prop) + { + return m_variablesChildren.TryGetValue(Tuple.Create(key.Item1, key.Item2), out prop); + } + + internal bool TryGetProperty((IDebugStackFrame2, string) key, out IDebugProperty2 prop) + { + return m_framesVariables.TryGetValue(Tuple.Create(key.Item1, key.Item2), out prop); + } + internal bool TryGet(int handle, out object value) { return m_variableHandles.TryGet(handle, out value); @@ -56,6 +77,16 @@ internal int Create(VariableScope scope) return m_variableHandles.Create(scope); } + public void AddChildVariable(int parentHandle, DEBUG_PROPERTY_INFO propInfo) + { + m_variablesChildren.Add(Tuple.Create(parentHandle, propInfo.bstrName), propInfo.pProperty); + } + + public void AddFrameVariable(IDebugStackFrame2 frame, DEBUG_PROPERTY_INFO propInfo) + { + m_framesVariables.Add(Tuple.Create(frame, propInfo.bstrName), propInfo.pProperty); + } + internal Variable CreateVariable(IDebugProperty2 property, enum_DEBUGPROP_INFO_FLAGS propertyInfoFlags) { var propertyInfo = new DEBUG_PROPERTY_INFO[1]; diff --git a/src/OpenDebugAD7/cppdbg.ad7Engine.json b/src/OpenDebugAD7/cppdbg.ad7Engine.json index 8fcf48ff2..51b8269bc 100644 --- a/src/OpenDebugAD7/cppdbg.ad7Engine.json +++ b/src/OpenDebugAD7/cppdbg.ad7Engine.json @@ -3,6 +3,7 @@ "engineClassName": "Microsoft.MIDebugEngine.AD7Engine", "conditionalBP": true, "functionBP": true, + "dataBP": true, "clipboardContext": true, "exceptionSettings": { "categories": [ diff --git a/src/SSHDebugPS/UI/ContainerPickerDialogWindow.xaml b/src/SSHDebugPS/UI/ContainerPickerDialogWindow.xaml index 6b3a214a3..c765009f6 100644 --- a/src/SSHDebugPS/UI/ContainerPickerDialogWindow.xaml +++ b/src/SSHDebugPS/UI/ContainerPickerDialogWindow.xaml @@ -225,12 +225,12 @@ + Value="{DynamicResource {x:Static platformui:EnvironmentColors.ControlLinkTextBrushKey}}" /> + Value="{DynamicResource {x:Static platformui:EnvironmentColors.ControlLinkTextBrushKey}}" /> @@ -240,7 +240,7 @@ + Value="{DynamicResource {x:Static platformui:EnvironmentColors.ControlLinkTextBrushKey}}" />