Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/debugger-libs.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonathan CHang <jonathan34c@gmail.com>2022-07-06 21:25:43 +0300
committerJonathan CHang <jonathan34c@gmail.com>2022-07-06 21:25:43 +0300
commite1bd562e518e9fa6943b4c4f562a07376ad5499e (patch)
treea2f25cc6d58f4c9443d0460d327f732c98f1a3ef
parent7cc29ac19969ffdda3844efd8f9984c90df7430e (diff)
clean failed unit test codedev/t-jochang/moduleSafe
-rw-r--r--Mono.Debugging.Soft/SoftDebuggerSession.cs6833
-rw-r--r--Mono.Debugging/Mono.Debugging.Client/Assembly.cs84
-rw-r--r--Mono.Debugging/Mono.Debugging.Client/AssemblyEventArgs.cs9
-rw-r--r--Mono.Debugging/Mono.Debugging.Client/BreakEventInfo.cs2
-rw-r--r--Mono.Debugging/Mono.Debugging.Client/Breakpoint.cs4
-rw-r--r--Mono.Debugging/Mono.Debugging.Client/DebuggerSession.cs39
-rw-r--r--Mono.Debugging/Mono.Debugging.Client/ProcessInfo.cs8
7 files changed, 3589 insertions, 3390 deletions
diff --git a/Mono.Debugging.Soft/SoftDebuggerSession.cs b/Mono.Debugging.Soft/SoftDebuggerSession.cs
index a173d25..abd65c1 100644
--- a/Mono.Debugging.Soft/SoftDebuggerSession.cs
+++ b/Mono.Debugging.Soft/SoftDebuggerSession.cs
@@ -1,391 +1,394 @@
-//
-// SoftDebuggerSession.cs
-//
-// Authors: Lluis Sanchez Gual <lluis@novell.com>
-// Jeffrey Stedfast <jeff@xamarin.com>
-//
-// Copyright (c) 2009 Novell, Inc (http://www.novell.com)
-// Copyright (c) 2012 Xamarin Inc. (http://www.xamarin.com)
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-//#define DEBUG_EVENT_QUEUEING
-
-using System;
-using System.IO;
-using System.Net;
-using System.Linq;
-using System.Text;
-using System.Threading;
-using System.Reflection;
-using System.Net.Sockets;
-using System.Globalization;
-using System.Collections.Generic;
-using System.Runtime.InteropServices;
-using System.Text.RegularExpressions;
-
-using Newtonsoft.Json;
-
-using Mono.Debugger.Soft;
-
-using Mono.Debugging.Client;
-using Mono.Debugging.Evaluation;
-
-using StackFrame = Mono.Debugger.Soft.StackFrame;
-
-namespace Mono.Debugging.Soft
-{
- public class SoftDebuggerSession : DebuggerSession
- {
+//
+// SoftDebuggerSession.cs
+//
+// Authors: Lluis Sanchez Gual <lluis@novell.com>
+// Jeffrey Stedfast <jeff@xamarin.com>
+//
+// Copyright (c) 2009 Novell, Inc (http://www.novell.com)
+// Copyright (c) 2012 Xamarin Inc. (http://www.xamarin.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//#define DEBUG_EVENT_QUEUEING
+
+using System;
+using System.IO;
+using System.Net;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Reflection;
+using System.Net.Sockets;
+using System.Globalization;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Text.RegularExpressions;
+
+using Newtonsoft.Json;
+
+using Mono.Debugger.Soft;
+
+using Mono.Debugging.Client;
+using Mono.Debugging.Evaluation;
+
+using StackFrame = Mono.Debugger.Soft.StackFrame;
+using Assembly = Mono.Debugging.Client.Assembly;
+using System.Collections.Immutable;
+namespace Mono.Debugging.Soft
+{
+ public class SoftDebuggerSession : DebuggerSession
+ {
readonly Dictionary<AppDomainMirror, HashSet<AssemblyMirror>> domainAssembliesToUnload = new Dictionary<AppDomainMirror, HashSet<AssemblyMirror>> ();
- readonly Dictionary<Tuple<TypeMirror, string>, MethodMirror[]> overloadResolveCache = new Dictionary<Tuple<TypeMirror, string>, MethodMirror[]> ();
- readonly Dictionary<string, List<TypeMirror>> source_to_type = new Dictionary<string, List<TypeMirror>> (PathComparer);
- readonly Dictionary<long,ObjectMirror> activeExceptionsByThread = new Dictionary<long, ObjectMirror> ();
- readonly Dictionary<EventRequest, BreakInfo> breakpoints = new Dictionary<EventRequest, BreakInfo> ();
+ readonly Dictionary<Tuple<TypeMirror, string>, MethodMirror[]> overloadResolveCache = new Dictionary<Tuple<TypeMirror, string>, MethodMirror[]> ();
+ readonly Dictionary<string, List<TypeMirror>> source_to_type = new Dictionary<string, List<TypeMirror>> (PathComparer);
+ readonly Dictionary<long,ObjectMirror> activeExceptionsByThread = new Dictionary<long, ObjectMirror> ();
+ readonly Dictionary<EventRequest, BreakInfo> breakpoints = new Dictionary<EventRequest, BreakInfo> ();
readonly Dictionary<TypeMirror, string[]> type_to_source = new Dictionary<TypeMirror, string[]> ();
- readonly Dictionary<string, List<TypeMirror>> aliases = new Dictionary<string, List<TypeMirror>> ();
- readonly Dictionary<string, List<TypeMirror>> types = new Dictionary<string, List<TypeMirror>> ();
- readonly LinkedList<List<Event>> queuedEventSets = new LinkedList<List<Event>> ();
- readonly Dictionary<long,long> localThreadIds = new Dictionary<long, long> ();
- readonly List<BreakInfo> pending_bes = new List<BreakInfo> ();
- TypeLoadEventRequest typeLoadReq, typeLoadTypeNameReq;
- ExceptionEventRequest unhandledExceptionRequest;
- Dictionary<string, string> assemblyPathMap;
+ readonly Dictionary<string, List<TypeMirror>> aliases = new Dictionary<string, List<TypeMirror>> ();
+ readonly Dictionary<string, List<TypeMirror>> types = new Dictionary<string, List<TypeMirror>> ();
+ readonly LinkedList<List<Event>> queuedEventSets = new LinkedList<List<Event>> ();
+ readonly Dictionary<long,long> localThreadIds = new Dictionary<long, long> ();
+ readonly List<BreakInfo> pending_bes = new List<BreakInfo> ();
+ TypeLoadEventRequest typeLoadReq, typeLoadTypeNameReq;
+ ExceptionEventRequest unhandledExceptionRequest;
+ Dictionary<string, string> assemblyPathMap;
Dictionary<string, string> symbolPathMap;
- ThreadMirror current_thread, recent_thread;
- List<AssemblyMirror> assemblyFilters;
- StepEventRequest currentStepRequest;
- IConnectionDialog connectionDialog;
- Thread outputReader, errorReader;
- bool loggedSymlinkedRuntimesBug;
- SoftDebuggerStartArgs startArgs;
- List<string> userAssemblyNames;
- ThreadInfo[] current_threads;
- string remoteProcessName;
- long currentAddress = -1;
- IAsyncResult connection;
- int currentStackDepth;
- ProcessInfo[] procs;
- Thread eventHandler;
- VirtualMachine vm;
- bool autoStepInto;
- bool disposed;
- bool started;
-
+ ThreadMirror current_thread, recent_thread;
+ List<AssemblyMirror> assemblyFilters;
+ StepEventRequest currentStepRequest;
+ IConnectionDialog connectionDialog;
+ Thread outputReader, errorReader;
+ bool loggedSymlinkedRuntimesBug;
+ SoftDebuggerStartArgs startArgs;
+ List<string> userAssemblyNames;
+ List<SourceUpdate> sourceUpdates;
+ ThreadInfo[] current_threads;
+ string remoteProcessName;
+ long currentAddress = -1;
+ IAsyncResult connection;
+ int currentStackDepth;
+ ProcessInfo[] procs;
+ Thread eventHandler;
+ VirtualMachine vm;
+ bool autoStepInto;
+ bool disposed;
+ bool started;
+
internal int StackVersion;
- public SoftDebuggerAdaptor Adaptor {
- get; private set;
- }
-
- public SoftDebuggerSession ()
- {
- Adaptor = CreateSoftDebuggerAdaptor ();
- Adaptor.BusyStateChanged += (sender, e) => SetBusyState (e);
- Adaptor.DebuggerSession = this;
- }
-
- protected virtual SoftDebuggerAdaptor CreateSoftDebuggerAdaptor ()
- {
- return new SoftDebuggerAdaptor ();
- }
-
- public Version ProtocolVersion {
- get { return new Version (vm.Version.MajorVersion, vm.Version.MinorVersion); }
- }
-
- protected override void OnRun (DebuggerStartInfo startInfo)
- {
- if (HasExited)
- throw new InvalidOperationException ("Already exited");
-
- SetupProcessStartHooks ();
-
- var dsi = (SoftDebuggerStartInfo) startInfo;
- if (dsi.StartArgs is SoftDebuggerLaunchArgs) {
- remoteProcessName = Path.GetFileNameWithoutExtension (dsi.Command);
- StartLaunching (dsi);
- } else if (dsi.StartArgs is SoftDebuggerConnectArgs) {
- StartConnecting (dsi);
- } else if (dsi.StartArgs is SoftDebuggerListenArgs) {
- StartListening (dsi);
- } else if (dsi.StartArgs.ConnectionProvider != null) {
- StartConnection (dsi);
- } else {
- throw new ArgumentException ("StartArgs has no ConnectionProvider");
- }
- }
-
- void StartConnection (SoftDebuggerStartInfo dsi)
- {
- startArgs = dsi.StartArgs;
-
- RegisterUserAssemblies (dsi);
-
- if (!String.IsNullOrEmpty (dsi.LogMessage))
- OnDebuggerOutput (false, dsi.LogMessage + Environment.NewLine);
-
- AsyncCallback callback = null;
- int attemptNumber = 0;
- int maxAttempts = startArgs.MaxConnectionAttempts;
- int timeBetweenAttempts = startArgs.TimeBetweenConnectionAttempts;
- callback = delegate (IAsyncResult ar) {
- try {
- string appName;
- VirtualMachine machine;
- startArgs.ConnectionProvider.EndConnect (ar, out machine, out appName);
- remoteProcessName = appName;
- ConnectionStarted (machine);
- return;
- } catch (Exception ex) {
- attemptNumber++;
- if (!ShouldRetryConnection (ex, attemptNumber)
- || startArgs?.ConnectionProvider?.ShouldRetryConnection (ex) == false
- || attemptNumber == maxAttempts
- || HasExited) {
- OnConnectionError (ex);
- return;
- }
- }
- try {
- if (timeBetweenAttempts > 0)
- Thread.Sleep (timeBetweenAttempts);
- ConnectionStarting (startArgs.ConnectionProvider.BeginConnect (dsi, callback), dsi, false, attemptNumber);
- } catch (Exception ex2) {
- OnConnectionError (ex2);
- }
- };
- //the "listening" value is never used, pass a dummy value
- ConnectionStarting (startArgs.ConnectionProvider.BeginConnect (dsi, callback), dsi, false, 0);
- }
-
- void StartLaunching (SoftDebuggerStartInfo dsi)
- {
- var args = (SoftDebuggerLaunchArgs) dsi.StartArgs;
- var executable = string.IsNullOrEmpty (args.MonoExecutableFileName) ? "mono" : args.MonoExecutableFileName;
- var runtime = string.IsNullOrEmpty (args.MonoRuntimePrefix) ? executable : Path.Combine (Path.Combine (args.MonoRuntimePrefix, "bin"), executable);
- RegisterUserAssemblies (dsi);
-
- var psi = new System.Diagnostics.ProcessStartInfo (runtime) {
- Arguments = string.Format ("{2} \"{0}\" {1}", dsi.Command, dsi.Arguments, dsi.RuntimeArguments),
- WorkingDirectory = dsi.WorkingDirectory,
- RedirectStandardOutput = true,
- RedirectStandardError = true,
- UseShellExecute = false,
- CreateNoWindow = true,
- };
-
- LaunchOptions options = null;
-
- if (dsi.UseExternalConsole && args.ExternalConsoleLauncher != null) {
- options = new LaunchOptions ();
- options.CustomTargetProcessLauncher = args.ExternalConsoleLauncher;
- psi.RedirectStandardOutput = false;
- psi.RedirectStandardError = false;
- }
- if (args.CustomProcessLauncher != null) {
- options = options ?? new LaunchOptions ();
- options.CustomProcessLauncher = args.CustomProcessLauncher;
- }
-
- var sdbLog = Environment.GetEnvironmentVariable ("MONODEVELOP_SDB_LOG");
- if (!string.IsNullOrEmpty (sdbLog)) {
- options = options ?? new LaunchOptions ();
- options.AgentArgs = string.Format ("loglevel=10,logfile='{0}',setpgid=y", sdbLog);
- }
-
- foreach (var env in args.MonoRuntimeEnvironmentVariables)
- psi.EnvironmentVariables[env.Key] = env.Value;
-
- foreach (var env in dsi.EnvironmentVariables)
- psi.EnvironmentVariables[env.Key] = env.Value;
-
- if (!string.IsNullOrEmpty (dsi.LogMessage))
- OnDebuggerOutput (false, dsi.LogMessage + Environment.NewLine);
-
- var callback = HandleConnectionCallbackErrors (ar => ConnectionStarted (VirtualMachineManager.EndLaunch (ar)));
- ConnectionStarting (VirtualMachineManager.BeginLaunch (psi, callback, options), dsi, true, 0);
- }
-
- /// <summary>Starts the debugger listening for a connection over TCP/IP</summary>
- protected void StartListening (SoftDebuggerStartInfo dsi)
- {
- int dp, cp;
- StartListening (dsi, out dp, out cp);
- }
-
- /// <summary>Starts the debugger listening for a connection over TCP/IP</summary>
- protected void StartListening (SoftDebuggerStartInfo dsi, out int assignedDebugPort)
- {
- int cp;
- StartListening (dsi, out assignedDebugPort, out cp);
- }
-
- /// <summary>Starts the debugger listening for a connection over TCP/IP</summary>
- protected void StartListening (SoftDebuggerStartInfo dsi, out int assignedDebugPort, out int assignedConsolePort)
- {
- IPEndPoint dbgEP, conEP;
- InitForRemoteSession (dsi, out dbgEP, out conEP);
-
- var callback = HandleConnectionCallbackErrors (ar => ConnectionStarted (VirtualMachineManager.EndListen (ar)));
- var a = VirtualMachineManager.BeginListen (dbgEP, conEP, callback, out assignedDebugPort, out assignedConsolePort);
- ConnectionStarting (a, dsi, true, 0);
- }
-
- protected virtual bool ShouldRetryConnection (Exception ex, int attemptNumber)
- {
- var sx = ex as SocketException;
- if (sx != null) {
- if (sx.ErrorCode == 10061) //connection refused
- return true;
- }
- //retry if it receives 0 byte and threw an exception in the handshake
- return true;
- }
-
- protected void StartConnecting (SoftDebuggerStartInfo dsi)
- {
- StartConnecting (dsi, dsi.StartArgs.MaxConnectionAttempts, dsi.StartArgs.TimeBetweenConnectionAttempts);
- }
-
- /// <summary>Starts the debugger connecting to a remote IP</summary>
- protected void StartConnecting (SoftDebuggerStartInfo dsi, int maxAttempts, int timeBetweenAttempts)
- {
- if (timeBetweenAttempts < 0 || timeBetweenAttempts > 10000)
- throw new ArgumentException ("timeBetweenAttempts");
-
- IPEndPoint dbgEP, conEP;
- InitForRemoteSession (dsi, out dbgEP, out conEP);
-
- AsyncCallback callback = null;
- int attemptNumber = 0;
- callback = delegate (IAsyncResult ar) {
- try {
- ConnectionStarted (VirtualMachineManager.EndConnect (ar));
- return;
- } catch (Exception ex) {
- attemptNumber++;
- if (!ShouldRetryConnection (ex, attemptNumber) || attemptNumber == maxAttempts || HasExited) {
- OnConnectionError (ex);
- return;
- }
- }
- try {
- if (timeBetweenAttempts > 0)
- Thread.Sleep (timeBetweenAttempts);
-
- ConnectionStarting (VirtualMachineManager.BeginConnect (dbgEP, conEP, callback), dsi, false, attemptNumber);
-
- } catch (Exception ex2) {
- OnConnectionError (ex2);
- }
- };
-
- ConnectionStarting (VirtualMachineManager.BeginConnect (dbgEP, conEP, callback), dsi, false, 0);
- }
-
- void InitForRemoteSession (SoftDebuggerStartInfo dsi, out IPEndPoint dbgEP, out IPEndPoint conEP)
- {
- if (remoteProcessName != null)
- throw new InvalidOperationException ("Cannot initialize connection more than once");
-
- var args = (SoftDebuggerRemoteArgs) dsi.StartArgs;
-
- remoteProcessName = args.AppName;
-
- RegisterUserAssemblies (dsi);
-
- dbgEP = new IPEndPoint (args.Address, args.DebugPort);
- conEP = args.RedirectOutput? new IPEndPoint (args.Address, args.OutputPort) : null;
-
- if (!String.IsNullOrEmpty (dsi.LogMessage))
- OnDebuggerOutput (false, dsi.LogMessage + Environment.NewLine);
- }
-
- ///<summary>Catches errors in async callbacks and hands off to OnConnectionError</summary>
- AsyncCallback HandleConnectionCallbackErrors (AsyncCallback callback)
- {
- return delegate (IAsyncResult ar) {
- connection = null;
- try {
- callback (ar);
- } catch (Exception ex) {
- OnConnectionError (ex);
- }
- };
- }
-
- /// <summary>
- /// Called if an error happens while making the connection. Default terminates the session.
- /// </summary>
- protected virtual void OnConnectionError (Exception ex)
- {
- //if the exception was caused by cancelling the session
- if (HasExited)
- return;
-
- if (!HandleException (new ConnectionException (ex))) {
- DebuggerLoggingService.LogAndShowException ("Unhandled error launching soft debugger", ex);
- }
-
- // The session is dead
- // HandleException doesn't actually handle exceptions, it just displays them.
- try {
- EndSession ();
- } catch (Exception e) {
- DebuggerLoggingService.LogError ("Unhandled error ending the debugger session", e);
- }
- }
-
- void ConnectionStarting (IAsyncResult connectionHandle, DebuggerStartInfo dsi, bool listening, int attemptNumber)
- {
- if (connection != null && (attemptNumber == 0 || !connection.IsCompleted))
- throw new InvalidOperationException ("Already connecting");
-
- connection = connectionHandle;
-
- if (attemptNumber == 0) {
- if (ConnectionDialogCreatorExtended != null)
- connectionDialog = ConnectionDialogCreatorExtended (dsi);
- else if (ConnectionDialogCreator != null)
- connectionDialog = ConnectionDialogCreator ();
-
- if (connectionDialog != null) {
- connectionDialog.UserCancelled += delegate {
- EndSession ();
- };
- }
- }
-
- if (connectionDialog != null)
- connectionDialog.SetMessage (dsi, GetConnectingMessage (dsi), listening, attemptNumber);
- }
-
- protected virtual string GetConnectingMessage (DebuggerStartInfo dsi)
- {
- return null;
- }
-
- void EndLaunch ()
- {
- HideConnectionDialog ();
- if (connection != null) {
+ public SoftDebuggerAdaptor Adaptor {
+ get; private set;
+ }
+
+ public SoftDebuggerSession ()
+ {
+ Adaptor = CreateSoftDebuggerAdaptor ();
+ Adaptor.BusyStateChanged += (sender, e) => SetBusyState (e);
+ Adaptor.DebuggerSession = this;
+ sourceUpdates = new List<SourceUpdate> ();
+ }
+
+ protected virtual SoftDebuggerAdaptor CreateSoftDebuggerAdaptor ()
+ {
+ return new SoftDebuggerAdaptor ();
+ }
+
+ public Version ProtocolVersion {
+ get { return new Version (vm.Version.MajorVersion, vm.Version.MinorVersion); }
+ }
+
+ protected override void OnRun (DebuggerStartInfo startInfo)
+ {
+ if (HasExited)
+ throw new InvalidOperationException ("Already exited");
+
+ SetupProcessStartHooks ();
+
+ var dsi = (SoftDebuggerStartInfo) startInfo;
+ if (dsi.StartArgs is SoftDebuggerLaunchArgs) {
+ remoteProcessName = Path.GetFileNameWithoutExtension (dsi.Command);
+ StartLaunching (dsi);
+ } else if (dsi.StartArgs is SoftDebuggerConnectArgs) {
+ StartConnecting (dsi);
+ } else if (dsi.StartArgs is SoftDebuggerListenArgs) {
+ StartListening (dsi);
+ } else if (dsi.StartArgs.ConnectionProvider != null) {
+ StartConnection (dsi);
+ } else {
+ throw new ArgumentException ("StartArgs has no ConnectionProvider");
+ }
+ }
+
+ void StartConnection (SoftDebuggerStartInfo dsi)
+ {
+ startArgs = dsi.StartArgs;
+
+ RegisterUserAssemblies (dsi);
+
+ if (!String.IsNullOrEmpty (dsi.LogMessage))
+ OnDebuggerOutput (false, dsi.LogMessage + Environment.NewLine);
+
+ AsyncCallback callback = null;
+ int attemptNumber = 0;
+ int maxAttempts = startArgs.MaxConnectionAttempts;
+ int timeBetweenAttempts = startArgs.TimeBetweenConnectionAttempts;
+ callback = delegate (IAsyncResult ar) {
+ try {
+ string appName;
+ VirtualMachine machine;
+ startArgs.ConnectionProvider.EndConnect (ar, out machine, out appName);
+ remoteProcessName = appName;
+ ConnectionStarted (machine);
+ return;
+ } catch (Exception ex) {
+ attemptNumber++;
+ if (!ShouldRetryConnection (ex, attemptNumber)
+ || startArgs?.ConnectionProvider?.ShouldRetryConnection (ex) == false
+ || attemptNumber == maxAttempts
+ || HasExited) {
+ OnConnectionError (ex);
+ return;
+ }
+ }
+ try {
+ if (timeBetweenAttempts > 0)
+ Thread.Sleep (timeBetweenAttempts);
+ ConnectionStarting (startArgs.ConnectionProvider.BeginConnect (dsi, callback), dsi, false, attemptNumber);
+ } catch (Exception ex2) {
+ OnConnectionError (ex2);
+ }
+ };
+ //the "listening" value is never used, pass a dummy value
+ ConnectionStarting (startArgs.ConnectionProvider.BeginConnect (dsi, callback), dsi, false, 0);
+ }
+
+ void StartLaunching (SoftDebuggerStartInfo dsi)
+ {
+ var args = (SoftDebuggerLaunchArgs) dsi.StartArgs;
+ var executable = string.IsNullOrEmpty (args.MonoExecutableFileName) ? "mono" : args.MonoExecutableFileName;
+ var runtime = string.IsNullOrEmpty (args.MonoRuntimePrefix) ? executable : Path.Combine (Path.Combine (args.MonoRuntimePrefix, "bin"), executable);
+ RegisterUserAssemblies (dsi);
+
+ var psi = new System.Diagnostics.ProcessStartInfo (runtime) {
+ Arguments = string.Format ("{2} \"{0}\" {1}", dsi.Command, dsi.Arguments, dsi.RuntimeArguments),
+ WorkingDirectory = dsi.WorkingDirectory,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ };
+
+ LaunchOptions options = null;
+
+ if (dsi.UseExternalConsole && args.ExternalConsoleLauncher != null) {
+ options = new LaunchOptions ();
+ options.CustomTargetProcessLauncher = args.ExternalConsoleLauncher;
+ psi.RedirectStandardOutput = false;
+ psi.RedirectStandardError = false;
+ }
+ if (args.CustomProcessLauncher != null) {
+ options = options ?? new LaunchOptions ();
+ options.CustomProcessLauncher = args.CustomProcessLauncher;
+ }
+
+ var sdbLog = Environment.GetEnvironmentVariable ("MONODEVELOP_SDB_LOG");
+ if (!string.IsNullOrEmpty (sdbLog)) {
+ options = options ?? new LaunchOptions ();
+ options.AgentArgs = string.Format ("loglevel=10,logfile='{0}',setpgid=y", sdbLog);
+ }
+
+ foreach (var env in args.MonoRuntimeEnvironmentVariables)
+ psi.EnvironmentVariables[env.Key] = env.Value;
+
+ foreach (var env in dsi.EnvironmentVariables)
+ psi.EnvironmentVariables[env.Key] = env.Value;
+
+ if (!string.IsNullOrEmpty (dsi.LogMessage))
+ OnDebuggerOutput (false, dsi.LogMessage + Environment.NewLine);
+
+ var callback = HandleConnectionCallbackErrors (ar => ConnectionStarted (VirtualMachineManager.EndLaunch (ar)));
+ ConnectionStarting (VirtualMachineManager.BeginLaunch (psi, callback, options), dsi, true, 0);
+ }
+
+ /// <summary>Starts the debugger listening for a connection over TCP/IP</summary>
+ protected void StartListening (SoftDebuggerStartInfo dsi)
+ {
+ int dp, cp;
+ StartListening (dsi, out dp, out cp);
+ }
+
+ /// <summary>Starts the debugger listening for a connection over TCP/IP</summary>
+ protected void StartListening (SoftDebuggerStartInfo dsi, out int assignedDebugPort)
+ {
+ int cp;
+ StartListening (dsi, out assignedDebugPort, out cp);
+ }
+
+ /// <summary>Starts the debugger listening for a connection over TCP/IP</summary>
+ protected void StartListening (SoftDebuggerStartInfo dsi, out int assignedDebugPort, out int assignedConsolePort)
+ {
+ IPEndPoint dbgEP, conEP;
+ InitForRemoteSession (dsi, out dbgEP, out conEP);
+
+ var callback = HandleConnectionCallbackErrors (ar => ConnectionStarted (VirtualMachineManager.EndListen (ar)));
+ var a = VirtualMachineManager.BeginListen (dbgEP, conEP, callback, out assignedDebugPort, out assignedConsolePort);
+ ConnectionStarting (a, dsi, true, 0);
+ }
+
+ protected virtual bool ShouldRetryConnection (Exception ex, int attemptNumber)
+ {
+ var sx = ex as SocketException;
+ if (sx != null) {
+ if (sx.ErrorCode == 10061) //connection refused
+ return true;
+ }
+ //retry if it receives 0 byte and threw an exception in the handshake
+ return true;
+ }
+
+ protected void StartConnecting (SoftDebuggerStartInfo dsi)
+ {
+ StartConnecting (dsi, dsi.StartArgs.MaxConnectionAttempts, dsi.StartArgs.TimeBetweenConnectionAttempts);
+ }
+
+ /// <summary>Starts the debugger connecting to a remote IP</summary>
+ protected void StartConnecting (SoftDebuggerStartInfo dsi, int maxAttempts, int timeBetweenAttempts)
+ {
+ if (timeBetweenAttempts < 0 || timeBetweenAttempts > 10000)
+ throw new ArgumentException ("timeBetweenAttempts");
+
+ IPEndPoint dbgEP, conEP;
+ InitForRemoteSession (dsi, out dbgEP, out conEP);
+
+ AsyncCallback callback = null;
+ int attemptNumber = 0;
+ callback = delegate (IAsyncResult ar) {
+ try {
+ ConnectionStarted (VirtualMachineManager.EndConnect (ar));
+ return;
+ } catch (Exception ex) {
+ attemptNumber++;
+ if (!ShouldRetryConnection (ex, attemptNumber) || attemptNumber == maxAttempts || HasExited) {
+ OnConnectionError (ex);
+ return;
+ }
+ }
+ try {
+ if (timeBetweenAttempts > 0)
+ Thread.Sleep (timeBetweenAttempts);
+
+ ConnectionStarting (VirtualMachineManager.BeginConnect (dbgEP, conEP, callback), dsi, false, attemptNumber);
+
+ } catch (Exception ex2) {
+ OnConnectionError (ex2);
+ }
+ };
+
+ ConnectionStarting (VirtualMachineManager.BeginConnect (dbgEP, conEP, callback), dsi, false, 0);
+ }
+
+ void InitForRemoteSession (SoftDebuggerStartInfo dsi, out IPEndPoint dbgEP, out IPEndPoint conEP)
+ {
+ if (remoteProcessName != null)
+ throw new InvalidOperationException ("Cannot initialize connection more than once");
+
+ var args = (SoftDebuggerRemoteArgs) dsi.StartArgs;
+
+ remoteProcessName = args.AppName;
+
+ RegisterUserAssemblies (dsi);
+
+ dbgEP = new IPEndPoint (args.Address, args.DebugPort);
+ conEP = args.RedirectOutput? new IPEndPoint (args.Address, args.OutputPort) : null;
+
+ if (!String.IsNullOrEmpty (dsi.LogMessage))
+ OnDebuggerOutput (false, dsi.LogMessage + Environment.NewLine);
+ }
+
+ ///<summary>Catches errors in async callbacks and hands off to OnConnectionError</summary>
+ AsyncCallback HandleConnectionCallbackErrors (AsyncCallback callback)
+ {
+ return delegate (IAsyncResult ar) {
+ connection = null;
+ try {
+ callback (ar);
+ } catch (Exception ex) {
+ OnConnectionError (ex);
+ }
+ };
+ }
+
+ /// <summary>
+ /// Called if an error happens while making the connection. Default terminates the session.
+ /// </summary>
+ protected virtual void OnConnectionError (Exception ex)
+ {
+ //if the exception was caused by cancelling the session
+ if (HasExited)
+ return;
+
+ if (!HandleException (new ConnectionException (ex))) {
+ DebuggerLoggingService.LogAndShowException ("Unhandled error launching soft debugger", ex);
+ }
+
+ // The session is dead
+ // HandleException doesn't actually handle exceptions, it just displays them.
+ try {
+ EndSession ();
+ } catch (Exception e) {
+ DebuggerLoggingService.LogError ("Unhandled error ending the debugger session", e);
+ }
+ }
+
+ void ConnectionStarting (IAsyncResult connectionHandle, DebuggerStartInfo dsi, bool listening, int attemptNumber)
+ {
+ if (connection != null && (attemptNumber == 0 || !connection.IsCompleted))
+ throw new InvalidOperationException ("Already connecting");
+
+ connection = connectionHandle;
+
+ if (attemptNumber == 0) {
+ if (ConnectionDialogCreatorExtended != null)
+ connectionDialog = ConnectionDialogCreatorExtended (dsi);
+ else if (ConnectionDialogCreator != null)
+ connectionDialog = ConnectionDialogCreator ();
+
+ if (connectionDialog != null) {
+ connectionDialog.UserCancelled += delegate {
+ EndSession ();
+ };
+ }
+ }
+
+ if (connectionDialog != null)
+ connectionDialog.SetMessage (dsi, GetConnectingMessage (dsi), listening, attemptNumber);
+ }
+
+ protected virtual string GetConnectingMessage (DebuggerStartInfo dsi)
+ {
+ return null;
+ }
+
+ void EndLaunch ()
+ {
+ HideConnectionDialog ();
+ if (connection != null) {
try {
if (startArgs != null && startArgs.ConnectionProvider != null) {
startArgs.ConnectionProvider.CancelConnect(connection);
@@ -396,248 +399,248 @@ namespace Mono.Debugging.Soft
} catch(Exception e) {
DebuggerLoggingService.LogError("Unhandled error canceling the debugger connection", e);
}
- connection = null;
- }
- }
-
- protected virtual void EndSession ()
- {
- if (!HasExited) {
- EndLaunch ();
- OnTargetEvent (new TargetEventArgs (TargetEventType.TargetExited));
- }
- }
-
- const string StartWithShellExecuteExMarker = "___Process_StartWithShellExecuteEx_Marker___";
- const string StartWithCreateProcessMarker = "___Process_StartWithCreateProcess_Marker__";
- const string SetProcessIdMarker = "___Process_SetProcessId_Marker__";
-
- void SetupProcessStartHooks ()
- {
- if (!Options.DebugSubprocesses)
- return;
-
- // Subprocess lauch is intercepted using 3 function breakpoints.
- // The first breakpoint is used to intercept calls to start a process with shell execute.
- var bp = Breakpoints.OfType< FunctionBreakpoint> ().FirstOrDefault (b => b.CustomActionId == StartWithShellExecuteExMarker);
- if (bp == null) {
- bp = new FunctionBreakpoint ("System.Diagnostics.Process.StartWithShellExecuteEx", "C#");
- bp.HitAction = HitAction.CustomAction;
- bp.CustomActionId = StartWithShellExecuteExMarker;
- bp.NonUserBreakpoint = true;
- Breakpoints.Add (bp);
- }
-
- // The second breakpoint is used to intercept calls to start a process with CreateProcess
- bp = Breakpoints.OfType<FunctionBreakpoint> ().FirstOrDefault (b => b.CustomActionId == StartWithCreateProcessMarker);
- if (bp == null) {
- bp = new FunctionBreakpoint ("System.Diagnostics.Process.StartWithCreateProcess", "C#");
- bp.HitAction = HitAction.CustomAction;
- bp.CustomActionId = StartWithCreateProcessMarker;
- bp.NonUserBreakpoint = true;
- Breakpoints.Add (bp);
- }
-
- // The third breakpoint is used to intercept the method that assigns an ID to the process. This is used to
- // retrieve the process id, and also as a signal that the process has started running
- bp = Breakpoints.OfType<FunctionBreakpoint> ().FirstOrDefault (b => b.CustomActionId == SetProcessIdMarker);
- if (bp == null) {
- bp = new FunctionBreakpoint ("System.Diagnostics.Process.SetProcessId", "C#");
- bp.HitAction = HitAction.CustomAction;
- bp.CustomActionId = SetProcessIdMarker;
- bp.NonUserBreakpoint = true;
- Breakpoints.Add (bp);
- }
- }
-
- bool HandleProcessStartHook (ThreadMirror thread, BreakEvent b)
- {
- if (!Options.DebugSubprocesses)
- return false;
-
- // Subprocess debugging is achieved by intercepting calls to start a process using function breakpoints.
- // This is achieved in 3 stages:
- // 1) Process start interception: this is done by adding a function breakpoint to Process.StartWithShellExecuteEx
- // and Process.StartWithCreateProcess. If the process being launched is a mono process a new debug session
- // is creaated, and the process start info object is patched to add the debugger agent configuration options.
- // 2) Process launch. This is actually done by the current executing process. The new process is bound to
- // the new debug session. However, to complete the debug session initialization we need an actual reference
- // to the process, so that the session can get the process id and subscribe process events, and that's
- // done in step 3.
- // 3) Process launched interception: this is done by adding a function breakpoint to Process.SetProcessId.
- // This method is called after starting the process, so at this point it is possible to get a reference
- // to the running process object and assign it to the debug session to complete the initialization.
-
- // A potential process start call has been intercepted.
-
- var evalOptions = EvaluationOptions.DefaultOptions.Clone ();
- evalOptions.AllowTargetInvoke = evalOptions.AllowMethodEvaluation = evalOptions.FlattenHierarchy = true;
- evalOptions.GroupPrivateMembers = evalOptions.GroupStaticMembers = evalOptions.AllowToStringCalls = evalOptions.EllipsizeStrings = false;
-
- if (b.CustomActionId == StartWithShellExecuteExMarker || b.CustomActionId == StartWithCreateProcessMarker) {
-
- // A project start call has been intercepted. We need to check if a mono process is being started.
-
- var fr = thread.GetFrames ()[0];
- var ctx = new SoftEvaluationContext (this, fr, evalOptions);
- var eval = ctx.Evaluator;
-
- var parameters = eval.GetParameters (ctx);
- var startInfo = parameters.First();
- var exe = (string) startInfo.GetChild ("FileName", evalOptions).GetRawValue (evalOptions);
-
- // The process being launched is a mono process if the target file is a .exe or .dll, or if the launcher is mono.
-
- var ext = Path.GetExtension (exe).ToLower ();
- if (ext == ".exe" || ext == ".dll" || Subprocess.IsMonoLauncher (exe)) {
-
- // If the runtime arguments are already setting up a debugger agent, don't override it
- var arguments = (string)startInfo.GetChild ("Arguments", evalOptions).GetRawValue (evalOptions);
- if (arguments.Contains ("--debugger-agent"))
- return false;
-
- // Create the new debug session
- Subprocess sp;
- lock (subprocessesStarting) {
- sp = new Subprocess (GetId (thread), this);
- subprocessesStarting.Add (sp);
- }
-
- if (sp.CreateSession (startInfo, evalOptions)) {
- // Notify that the new session has started
- OnSubprocessStarted (sp.SubprocessSession);
- }
- }
- return true;
- } else if (b.CustomActionId == SetProcessIdMarker) {
-
- // A call to SetProcessId has been intercepted
- // Check if there is a subprocess waiting for the start signal
-
- var tid = GetId (thread);
- Subprocess subprocess;
-
- lock (subprocessesStarting)
- subprocess = subprocessesStarting.FirstOrDefault (s => s.ThreadId == tid);
-
- if (subprocess != null) {
- lock (subprocessesStarting)
- subprocessesStarting.Remove (subprocess);
-
- var fr = thread.GetFrames ()[0];
- var ctx = new SoftEvaluationContext (this, fr, evalOptions);
- var eval = ctx.Evaluator;
-
- try {
- // The first parameter is the process ID
- var parameters = eval.GetParameters (ctx);
- var id = (int)parameters.First ().GetRawValue (evalOptions);
-
- // Get the start info object from the process
- var ts = eval.GetThisReference (ctx);
- var si = ts.GetChild ("startInfo", evalOptions);
-
- // Tell the subprocess that the process has started. This will finish the initialization
- // of the debug session.
- subprocess.SetStarted (si, id, ctx);
- } catch {
- subprocess.Shutdown ();
- throw;
- }
- }
- return true;
- }
- return false;
- }
-
- List<Subprocess> subprocessesStarting = new List<Subprocess> ();
-
- public Dictionary<Tuple<TypeMirror, string>, MethodMirror[]> OverloadResolveCache {
- get {
- return overloadResolveCache;
- }
- }
-
- void HideConnectionDialog ()
- {
- if (connectionDialog != null) {
- connectionDialog.Dispose ();
- connectionDialog = null;
- }
- }
-
- /// <summary>
- /// If subclasses do an async connect in OnRun, they should pass the resulting VM to this method.
- /// If the vm is null, the session will be closed.
- /// </summary>
- void ConnectionStarted (VirtualMachine machine)
- {
- if (vm != null)
- throw new InvalidOperationException ("The VM has already connected");
-
- if (machine == null) {
- EndSession ();
- return;
- }
-
- connection = null;
-
- vm = machine;
-
- // Allocate the process list now, so it is cached and can be queried while the session is running
- OnGetProcesses ();
-
- ConnectOutput (machine.StandardOutput, false);
- ConnectOutput (machine.StandardError, true);
-
- HideConnectionDialog ();
-
- machine.EnableEvents (EventType.AssemblyLoad, EventType.ThreadStart, EventType.ThreadDeath,
- EventType.AppDomainUnload, EventType.UserBreak, EventType.UserLog);
- try {
- unhandledExceptionRequest = machine.CreateExceptionRequest (null, false, true);
- unhandledExceptionRequest.Enable ();
- } catch (NotSupportedException) {
- //Mono < 2.6.3 doesn't support catching unhandled exceptions
- }
-
- if (machine.Version.AtLeast (2, 9)) {
- /* Created later */
- } else {
- machine.EnableEvents (EventType.TypeLoad);
- }
-
-
- machine.EnableEvents (EventType.MethodUpdate);
-
- started = true;
-
- /* Wait for the VMStart event */
- HandleEventSet (machine.GetNextEventSet ());
-
- eventHandler = new Thread (EventHandler);
- eventHandler.Name = "SDB Event Handler";
- eventHandler.IsBackground = true;
- eventHandler.Start ();
- }
-
- void RegisterUserAssemblies (SoftDebuggerStartInfo dsi)
- {
- if (Options.ProjectAssembliesOnly && dsi.UserAssemblyNames != null) {
- assemblyFilters = new List<AssemblyMirror> ();
- userAssemblyNames = dsi.UserAssemblyNames.Select (x => x.ToString ()).ToList ();
- }
-
- assemblyPathMap = dsi.AssemblyPathMap;
- if (assemblyPathMap == null)
- assemblyPathMap = new Dictionary<string, string> ();
-
- if (dsi.SymbolPathMap == null)
- symbolPathMap = new Dictionary<string, string>();
- else
- symbolPathMap = dsi.SymbolPathMap;
- }
-
+ connection = null;
+ }
+ }
+
+ protected virtual void EndSession ()
+ {
+ if (!HasExited) {
+ EndLaunch ();
+ OnTargetEvent (new TargetEventArgs (TargetEventType.TargetExited));
+ }
+ }
+
+ const string StartWithShellExecuteExMarker = "___Process_StartWithShellExecuteEx_Marker___";
+ const string StartWithCreateProcessMarker = "___Process_StartWithCreateProcess_Marker__";
+ const string SetProcessIdMarker = "___Process_SetProcessId_Marker__";
+
+ void SetupProcessStartHooks ()
+ {
+ if (!Options.DebugSubprocesses)
+ return;
+
+ // Subprocess lauch is intercepted using 3 function breakpoints.
+ // The first breakpoint is used to intercept calls to start a process with shell execute.
+ var bp = Breakpoints.OfType< FunctionBreakpoint> ().FirstOrDefault (b => b.CustomActionId == StartWithShellExecuteExMarker);
+ if (bp == null) {
+ bp = new FunctionBreakpoint ("System.Diagnostics.Process.StartWithShellExecuteEx", "C#");
+ bp.HitAction = HitAction.CustomAction;
+ bp.CustomActionId = StartWithShellExecuteExMarker;
+ bp.NonUserBreakpoint = true;
+ Breakpoints.Add (bp);
+ }
+
+ // The second breakpoint is used to intercept calls to start a process with CreateProcess
+ bp = Breakpoints.OfType<FunctionBreakpoint> ().FirstOrDefault (b => b.CustomActionId == StartWithCreateProcessMarker);
+ if (bp == null) {
+ bp = new FunctionBreakpoint ("System.Diagnostics.Process.StartWithCreateProcess", "C#");
+ bp.HitAction = HitAction.CustomAction;
+ bp.CustomActionId = StartWithCreateProcessMarker;
+ bp.NonUserBreakpoint = true;
+ Breakpoints.Add (bp);
+ }
+
+ // The third breakpoint is used to intercept the method that assigns an ID to the process. This is used to
+ // retrieve the process id, and also as a signal that the process has started running
+ bp = Breakpoints.OfType<FunctionBreakpoint> ().FirstOrDefault (b => b.CustomActionId == SetProcessIdMarker);
+ if (bp == null) {
+ bp = new FunctionBreakpoint ("System.Diagnostics.Process.SetProcessId", "C#");
+ bp.HitAction = HitAction.CustomAction;
+ bp.CustomActionId = SetProcessIdMarker;
+ bp.NonUserBreakpoint = true;
+ Breakpoints.Add (bp);
+ }
+ }
+
+ bool HandleProcessStartHook (ThreadMirror thread, BreakEvent b)
+ {
+ if (!Options.DebugSubprocesses)
+ return false;
+
+ // Subprocess debugging is achieved by intercepting calls to start a process using function breakpoints.
+ // This is achieved in 3 stages:
+ // 1) Process start interception: this is done by adding a function breakpoint to Process.StartWithShellExecuteEx
+ // and Process.StartWithCreateProcess. If the process being launched is a mono process a new debug session
+ // is creaated, and the process start info object is patched to add the debugger agent configuration options.
+ // 2) Process launch. This is actually done by the current executing process. The new process is bound to
+ // the new debug session. However, to complete the debug session initialization we need an actual reference
+ // to the process, so that the session can get the process id and subscribe process events, and that's
+ // done in step 3.
+ // 3) Process launched interception: this is done by adding a function breakpoint to Process.SetProcessId.
+ // This method is called after starting the process, so at this point it is possible to get a reference
+ // to the running process object and assign it to the debug session to complete the initialization.
+
+ // A potential process start call has been intercepted.
+
+ var evalOptions = EvaluationOptions.DefaultOptions.Clone ();
+ evalOptions.AllowTargetInvoke = evalOptions.AllowMethodEvaluation = evalOptions.FlattenHierarchy = true;
+ evalOptions.GroupPrivateMembers = evalOptions.GroupStaticMembers = evalOptions.AllowToStringCalls = evalOptions.EllipsizeStrings = false;
+
+ if (b.CustomActionId == StartWithShellExecuteExMarker || b.CustomActionId == StartWithCreateProcessMarker) {
+
+ // A project start call has been intercepted. We need to check if a mono process is being started.
+
+ var fr = thread.GetFrames ()[0];
+ var ctx = new SoftEvaluationContext (this, fr, evalOptions);
+ var eval = ctx.Evaluator;
+
+ var parameters = eval.GetParameters (ctx);
+ var startInfo = parameters.First();
+ var exe = (string) startInfo.GetChild ("FileName", evalOptions).GetRawValue (evalOptions);
+
+ // The process being launched is a mono process if the target file is a .exe or .dll, or if the launcher is mono.
+
+ var ext = Path.GetExtension (exe).ToLower ();
+ if (ext == ".exe" || ext == ".dll" || Subprocess.IsMonoLauncher (exe)) {
+
+ // If the runtime arguments are already setting up a debugger agent, don't override it
+ var arguments = (string)startInfo.GetChild ("Arguments", evalOptions).GetRawValue (evalOptions);
+ if (arguments.Contains ("--debugger-agent"))
+ return false;
+
+ // Create the new debug session
+ Subprocess sp;
+ lock (subprocessesStarting) {
+ sp = new Subprocess (GetId (thread), this);
+ subprocessesStarting.Add (sp);
+ }
+
+ if (sp.CreateSession (startInfo, evalOptions)) {
+ // Notify that the new session has started
+ OnSubprocessStarted (sp.SubprocessSession);
+ }
+ }
+ return true;
+ } else if (b.CustomActionId == SetProcessIdMarker) {
+
+ // A call to SetProcessId has been intercepted
+ // Check if there is a subprocess waiting for the start signal
+
+ var tid = GetId (thread);
+ Subprocess subprocess;
+
+ lock (subprocessesStarting)
+ subprocess = subprocessesStarting.FirstOrDefault (s => s.ThreadId == tid);
+
+ if (subprocess != null) {
+ lock (subprocessesStarting)
+ subprocessesStarting.Remove (subprocess);
+
+ var fr = thread.GetFrames ()[0];
+ var ctx = new SoftEvaluationContext (this, fr, evalOptions);
+ var eval = ctx.Evaluator;
+
+ try {
+ // The first parameter is the process ID
+ var parameters = eval.GetParameters (ctx);
+ var id = (int)parameters.First ().GetRawValue (evalOptions);
+
+ // Get the start info object from the process
+ var ts = eval.GetThisReference (ctx);
+ var si = ts.GetChild ("startInfo", evalOptions);
+
+ // Tell the subprocess that the process has started. This will finish the initialization
+ // of the debug session.
+ subprocess.SetStarted (si, id, ctx);
+ } catch {
+ subprocess.Shutdown ();
+ throw;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ List<Subprocess> subprocessesStarting = new List<Subprocess> ();
+
+ public Dictionary<Tuple<TypeMirror, string>, MethodMirror[]> OverloadResolveCache {
+ get {
+ return overloadResolveCache;
+ }
+ }
+
+ void HideConnectionDialog ()
+ {
+ if (connectionDialog != null) {
+ connectionDialog.Dispose ();
+ connectionDialog = null;
+ }
+ }
+
+ /// <summary>
+ /// If subclasses do an async connect in OnRun, they should pass the resulting VM to this method.
+ /// If the vm is null, the session will be closed.
+ /// </summary>
+ void ConnectionStarted (VirtualMachine machine)
+ {
+ if (vm != null)
+ throw new InvalidOperationException ("The VM has already connected");
+
+ if (machine == null) {
+ EndSession ();
+ return;
+ }
+
+ connection = null;
+
+ vm = machine;
+
+ // Allocate the process list now, so it is cached and can be queried while the session is running
+ OnGetProcesses ();
+
+ ConnectOutput (machine.StandardOutput, false);
+ ConnectOutput (machine.StandardError, true);
+
+ HideConnectionDialog ();
+
+ machine.EnableEvents (EventType.AssemblyLoad, EventType.ThreadStart, EventType.ThreadDeath,
+ EventType.AppDomainUnload, EventType.UserBreak, EventType.UserLog);
+ try {
+ unhandledExceptionRequest = machine.CreateExceptionRequest (null, false, true);
+ unhandledExceptionRequest.Enable ();
+ } catch (NotSupportedException) {
+ //Mono < 2.6.3 doesn't support catching unhandled exceptions
+ }
+
+ if (machine.Version.AtLeast (2, 9)) {
+ /* Created later */
+ } else {
+ machine.EnableEvents (EventType.TypeLoad);
+ }
+
+
+ machine.EnableEvents (EventType.MethodUpdate);
+
+ started = true;
+
+ /* Wait for the VMStart event */
+ HandleEventSet (machine.GetNextEventSet ());
+
+ eventHandler = new Thread (EventHandler);
+ eventHandler.Name = "SDB Event Handler";
+ eventHandler.IsBackground = true;
+ eventHandler.Start ();
+ }
+
+ void RegisterUserAssemblies (SoftDebuggerStartInfo dsi)
+ {
+ if (Options.ProjectAssembliesOnly && dsi.UserAssemblyNames != null) {
+ assemblyFilters = new List<AssemblyMirror> ();
+ userAssemblyNames = dsi.UserAssemblyNames.Select (x => x.ToString ()).ToList ();
+ }
+
+ assemblyPathMap = dsi.AssemblyPathMap;
+ if (assemblyPathMap == null)
+ assemblyPathMap = new Dictionary<string, string> ();
+
+ if (dsi.SymbolPathMap == null)
+ symbolPathMap = new Dictionary<string, string>();
+ else
+ symbolPathMap = dsi.SymbolPathMap;
+ }
+
public void AddOrReplaceAssemblyPathMap(string assembly, string path)
{
assemblyPathMap[assembly] = path;
@@ -648,713 +651,713 @@ namespace Mono.Debugging.Soft
symbolPathMap[assembly] = path;
}
- protected bool SetSocketTimeouts (int sendTimeout, int receiveTimeout, int keepaliveInterval)
- {
- try {
- if (vm.Version.AtLeast (2, 4)) {
- vm.EnableEvents (EventType.KeepAlive);
- vm.SetSocketTimeouts (sendTimeout, receiveTimeout, keepaliveInterval);
- return true;
- }
-
- return false;
- } catch {
- return false;
- }
- }
-
- protected void ConnectOutput (StreamReader reader, bool error)
- {
- Thread t = (error ? errorReader : outputReader);
- if (t != null || reader == null)
- return;
-
- t = new Thread (() => ReadOutput (reader, error));
- t.Name = error ? "SDB error reader" : "SDB output reader";
- t.IsBackground = true;
- t.Start ();
-
- if (error)
- errorReader = t;
- else
- outputReader = t;
- }
-
- void ReadOutput (TextReader reader, bool isError)
- {
- try {
- var buffer = new char [1024];
- while (!HasExited) {
- int c = reader.Read (buffer, 0, buffer.Length);
- if (c > 0) {
- OnTargetOutput (isError, new string (buffer, 0, c));
- } else {
- //FIXME: workaround for buggy console stream that never blocks
- Thread.Sleep (250);
- }
- }
- } catch (IOException) {
- // Ignore
- }
- }
-
- protected virtual void OnResumed ()
- {
- current_threads = null;
- current_thread = null;
- activeExceptionsByThread.Clear ();
- }
-
- public VirtualMachine VirtualMachine {
- get { return vm; }
- }
-
- public TypeMirror GetType (string fullName)
- {
- List<TypeMirror> typesList;
-
- if (!types.TryGetValue (fullName, out typesList))
- aliases.TryGetValue (fullName, out typesList);
- if (typesList == null)
- return null;
- if (typesList.Count == 1)
- return typesList [0];
- //Idea here is... If we have multiple types with same name... they must be in different .dlls
- //so find 1st matching assembly with stackframes and then return type that belongs to that assembly
- var assembly = current_thread.GetFrames ().Select (f => f.Method.DeclaringType.Assembly).FirstOrDefault (asm => typesList.Select (t => t.Assembly).Contains (asm));
- return typesList.FirstOrDefault (t => t.Assembly == assembly) ?? typesList.FirstOrDefault ();
- }
-
- public IEnumerable<TypeMirror> GetAllTypes ()
- {
- return types.Values.SelectMany (l => l);
- }
-
- protected override bool AllowBreakEventChanges {
- get { return true; }
- }
-
- public override void Dispose ()
- {
- base.Dispose ();
-
- if (disposed)
- return;
-
- disposed = true;
-
- lock (subprocessesStarting) {
- foreach (var s in subprocessesStarting)
- s.Shutdown ();
- }
-
- if (!HasExited)
- EndLaunch ();
-
- exceptionRequests.Clear();
-
- if (!HasExited) {
- if (vm != null) {
- ThreadPool.QueueUserWorkItem (delegate {
- try {
- vm.Exit (0);
- } catch (VMDisconnectedException) {
- } catch (Exception ex) {
- DebuggerLoggingService.LogError ("Error exiting SDB VM:", ex);
- }
- });
- }
- }
-
- Adaptor.Dispose ();
- }
-
- protected override void OnAttachToProcess (long processId)
- {
- throw new NotSupportedException ();
- }
-
- protected override void OnContinue ()
- {
- ThreadPool.QueueUserWorkItem (delegate {
- try {
- Adaptor.CancelAsyncOperations (); // This call can block, so it has to run in background thread to avoid keeping the main session lock
- OnResumed ();
- vm.Resume ();
- DequeueEventsForFirstThread ();
- } catch (Exception ex) {
- if (!HandleException (ex) && outputOptions.ExceptionMessage)
- OnDebuggerOutput (true, ex.ToString ());
- }
- });
- }
-
- protected override void OnDetach ()
- {
- vm.Detach ();
- }
-
- protected override void OnExit ()
- {
- HasExited = true;
- EndLaunch ();
- if (vm != null) {
- try {
- vm.Exit (0);
- } catch (VMDisconnectedException) {
- // The VM was already disconnected, ignore.
- } catch (SocketException se) {
- // This will often happen during normal operation
- DebuggerLoggingService.LogError ("Error closing debugger session", se);
- } catch (IOException ex) {
- // This will often happen during normal operation
- DebuggerLoggingService.LogError ("Error closing debugger session", ex);
- }
- }
- QueueEnsureExited ();
- }
-
- void QueueEnsureExited ()
- {
- if (vm != null) {
- //FIXME: this might never get reached if the IDE is Exited first
- try {
- if (vm.Process != null) {
- ThreadPool.QueueUserWorkItem (delegate {
- // This is a workaround for a mono bug
- // Without this call, the process may become zombie in mono < 2.10.2
- vm.Process.WaitForExit ();
- });
- }
- } catch (InvalidOperationException) {
- // ignore - this is thrown by the vm.Process getter when the process has already exited
- } catch (Exception ex) {
- DebuggerLoggingService.LogError ("Failed to launch a thread to wait for the process to exit", ex);
- }
-
- var t = new System.Timers.Timer ();
- t.Interval = 3000;
- t.Elapsed += delegate {
- try {
- t.Enabled = false;
- t.Dispose ();
- EnsureExited ();
- } catch (Exception ex) {
- DebuggerLoggingService.LogError ("Failed to force-terminate process", ex);
- }
-
- try {
- if (vm != null) {
- //this is a no-op if it already closed
- vm.ForceDisconnect ();
- }
- } catch (Exception ex) {
- DebuggerLoggingService.LogError ("Failed to force-close debugger connection", ex);
- }
- };
-
- t.Enabled = true;
- }
- }
-
- /// <summary>This is a fallback in case the debugger agent doesn't respond to an exit call</summary>
- protected virtual void EnsureExited ()
- {
- try {
- if (vm != null && vm.TargetProcess != null && !vm.TargetProcess.HasExited)
- vm.TargetProcess.Kill ();
- } catch (Exception ex) {
- DebuggerLoggingService.LogError ("Error force-terminating soft debugger process", ex);
- }
- }
-
- protected override void OnFinish ()
- {
- Step (StepDepth.Out, StepSize.Line);
- }
-
- protected override ProcessInfo[] OnGetProcesses ()
- {
- if (procs == null) {
- if (vm == null)//process didn't start yet
- return new ProcessInfo [0];
- if (vm.TargetProcess == null) {
- procs = new [] { new ProcessInfo (0, remoteProcessName ?? "mono") };
- } else {
- try {
- procs = new [] { new ProcessInfo (vm.TargetProcess.Id, remoteProcessName ?? vm.TargetProcess.ProcessName) };
- } catch (Exception ex) {
- if (!loggedSymlinkedRuntimesBug) {
- loggedSymlinkedRuntimesBug = true;
- DebuggerLoggingService.LogError ("Error getting debugger process info. Known Mono bug with symlinked runtimes.", ex);
- }
- procs = new [] { new ProcessInfo (0, "mono") };
- }
- }
- }
- return new [] { new ProcessInfo (procs[0].Id, procs[0].Name) };
- }
-
- internal PortablePdbData GetPdbData (AssemblyMirror asm)
- {
- string pdbFileName;
- if (!symbolPathMap.TryGetValue(asm.GetName().FullName, out pdbFileName) || Path.GetExtension(pdbFileName) != ".pdb")
+ protected bool SetSocketTimeouts (int sendTimeout, int receiveTimeout, int keepaliveInterval)
+ {
+ try {
+ if (vm.Version.AtLeast (2, 4)) {
+ vm.EnableEvents (EventType.KeepAlive);
+ vm.SetSocketTimeouts (sendTimeout, receiveTimeout, keepaliveInterval);
+ return true;
+ }
+
+ return false;
+ } catch {
+ return false;
+ }
+ }
+
+ protected void ConnectOutput (StreamReader reader, bool error)
+ {
+ Thread t = (error ? errorReader : outputReader);
+ if (t != null || reader == null)
+ return;
+
+ t = new Thread (() => ReadOutput (reader, error));
+ t.Name = error ? "SDB error reader" : "SDB output reader";
+ t.IsBackground = true;
+ t.Start ();
+
+ if (error)
+ errorReader = t;
+ else
+ outputReader = t;
+ }
+
+ void ReadOutput (TextReader reader, bool isError)
+ {
+ try {
+ var buffer = new char [1024];
+ while (!HasExited) {
+ int c = reader.Read (buffer, 0, buffer.Length);
+ if (c > 0) {
+ OnTargetOutput (isError, new string (buffer, 0, c));
+ } else {
+ //FIXME: workaround for buggy console stream that never blocks
+ Thread.Sleep (250);
+ }
+ }
+ } catch (IOException) {
+ // Ignore
+ }
+ }
+
+ protected virtual void OnResumed ()
+ {
+ current_threads = null;
+ current_thread = null;
+ activeExceptionsByThread.Clear ();
+ }
+
+ public VirtualMachine VirtualMachine {
+ get { return vm; }
+ }
+
+ public TypeMirror GetType (string fullName)
+ {
+ List<TypeMirror> typesList;
+
+ if (!types.TryGetValue (fullName, out typesList))
+ aliases.TryGetValue (fullName, out typesList);
+ if (typesList == null)
+ return null;
+ if (typesList.Count == 1)
+ return typesList [0];
+ //Idea here is... If we have multiple types with same name... they must be in different .dlls
+ //so find 1st matching assembly with stackframes and then return type that belongs to that assembly
+ var assembly = current_thread.GetFrames ().Select (f => f.Method.DeclaringType.Assembly).FirstOrDefault (asm => typesList.Select (t => t.Assembly).Contains (asm));
+ return typesList.FirstOrDefault (t => t.Assembly == assembly) ?? typesList.FirstOrDefault ();
+ }
+
+ public IEnumerable<TypeMirror> GetAllTypes ()
+ {
+ return types.Values.SelectMany (l => l);
+ }
+
+ protected override bool AllowBreakEventChanges {
+ get { return true; }
+ }
+
+ public override void Dispose ()
+ {
+ base.Dispose ();
+
+ if (disposed)
+ return;
+
+ disposed = true;
+
+ lock (subprocessesStarting) {
+ foreach (var s in subprocessesStarting)
+ s.Shutdown ();
+ }
+
+ if (!HasExited)
+ EndLaunch ();
+
+ exceptionRequests.Clear();
+
+ if (!HasExited) {
+ if (vm != null) {
+ ThreadPool.QueueUserWorkItem (delegate {
+ try {
+ vm.Exit (0);
+ } catch (VMDisconnectedException) {
+ } catch (Exception ex) {
+ DebuggerLoggingService.LogError ("Error exiting SDB VM:", ex);
+ }
+ });
+ }
+ }
+
+ Adaptor.Dispose ();
+ }
+
+ protected override void OnAttachToProcess (long processId)
+ {
+ throw new NotSupportedException ();
+ }
+
+ protected override void OnContinue ()
+ {
+ ThreadPool.QueueUserWorkItem (delegate {
+ try {
+ Adaptor.CancelAsyncOperations (); // This call can block, so it has to run in background thread to avoid keeping the main session lock
+ OnResumed ();
+ vm.Resume ();
+ DequeueEventsForFirstThread ();
+ } catch (Exception ex) {
+ if (!HandleException (ex) && outputOptions.ExceptionMessage)
+ OnDebuggerOutput (true, ex.ToString ());
+ }
+ });
+ }
+
+ protected override void OnDetach ()
+ {
+ vm.Detach ();
+ }
+
+ protected override void OnExit ()
+ {
+ HasExited = true;
+ EndLaunch ();
+ if (vm != null) {
+ try {
+ vm.Exit (0);
+ } catch (VMDisconnectedException) {
+ // The VM was already disconnected, ignore.
+ } catch (SocketException se) {
+ // This will often happen during normal operation
+ DebuggerLoggingService.LogError ("Error closing debugger session", se);
+ } catch (IOException ex) {
+ // This will often happen during normal operation
+ DebuggerLoggingService.LogError ("Error closing debugger session", ex);
+ }
+ }
+ QueueEnsureExited ();
+ }
+
+ void QueueEnsureExited ()
+ {
+ if (vm != null) {
+ //FIXME: this might never get reached if the IDE is Exited first
+ try {
+ if (vm.Process != null) {
+ ThreadPool.QueueUserWorkItem (delegate {
+ // This is a workaround for a mono bug
+ // Without this call, the process may become zombie in mono < 2.10.2
+ vm.Process.WaitForExit ();
+ });
+ }
+ } catch (InvalidOperationException) {
+ // ignore - this is thrown by the vm.Process getter when the process has already exited
+ } catch (Exception ex) {
+ DebuggerLoggingService.LogError ("Failed to launch a thread to wait for the process to exit", ex);
+ }
+
+ var t = new System.Timers.Timer ();
+ t.Interval = 3000;
+ t.Elapsed += delegate {
+ try {
+ t.Enabled = false;
+ t.Dispose ();
+ EnsureExited ();
+ } catch (Exception ex) {
+ DebuggerLoggingService.LogError ("Failed to force-terminate process", ex);
+ }
+
+ try {
+ if (vm != null) {
+ //this is a no-op if it already closed
+ vm.ForceDisconnect ();
+ }
+ } catch (Exception ex) {
+ DebuggerLoggingService.LogError ("Failed to force-close debugger connection", ex);
+ }
+ };
+
+ t.Enabled = true;
+ }
+ }
+
+ /// <summary>This is a fallback in case the debugger agent doesn't respond to an exit call</summary>
+ protected virtual void EnsureExited ()
+ {
+ try {
+ if (vm != null && vm.TargetProcess != null && !vm.TargetProcess.HasExited)
+ vm.TargetProcess.Kill ();
+ } catch (Exception ex) {
+ DebuggerLoggingService.LogError ("Error force-terminating soft debugger process", ex);
+ }
+ }
+
+ protected override void OnFinish ()
+ {
+ Step (StepDepth.Out, StepSize.Line);
+ }
+
+ protected override ProcessInfo[] OnGetProcesses ()
+ {
+ if (procs == null) {
+ if (vm == null)//process didn't start yet
+ return new ProcessInfo [0];
+ if (vm.TargetProcess == null) {
+ procs = new [] { new ProcessInfo (0, remoteProcessName ?? "mono") };
+ } else {
+ try {
+ procs = new [] { new ProcessInfo (vm.TargetProcess.Id, remoteProcessName ?? vm.TargetProcess.ProcessName) };
+ } catch (Exception ex) {
+ if (!loggedSymlinkedRuntimesBug) {
+ loggedSymlinkedRuntimesBug = true;
+ DebuggerLoggingService.LogError ("Error getting debugger process info. Known Mono bug with symlinked runtimes.", ex);
+ }
+ procs = new [] { new ProcessInfo (0, "mono") };
+ }
+ }
+ }
+ return new [] { new ProcessInfo (procs[0].Id, procs[0].Name) };
+ }
+
+ internal PortablePdbData GetPdbData (AssemblyMirror asm)
+ {
+ string pdbFileName;
+ if (!symbolPathMap.TryGetValue(asm.GetName().FullName, out pdbFileName) || Path.GetExtension(pdbFileName) != ".pdb")
{
string assemblyFileName;
- if (!assemblyPathMap.TryGetValue (asm.GetName ().FullName, out assemblyFileName))
- assemblyFileName = asm.Location;
- pdbFileName = Path.ChangeExtension (assemblyFileName, ".pdb");
+ if (!assemblyPathMap.TryGetValue (asm.GetName ().FullName, out assemblyFileName))
+ assemblyFileName = asm.Location;
+ pdbFileName = Path.ChangeExtension (assemblyFileName, ".pdb");
}
if (PortablePdbData.IsPortablePdb (pdbFileName))
return new PortablePdbData (pdbFileName);
// Attempt to fetch pdb from the debuggee over the wire
var pdbBlob = asm.GetPdbBlob ();
- return pdbBlob != null ? new PortablePdbData (pdbBlob) : null;
- }
-
- internal PortablePdbData GetPdbData (MethodMirror method)
- {
- return GetPdbData (method.DeclaringType.Assembly);
- }
-
- readonly Dictionary<AssemblyMirror, SourceLinkMap []> SourceLinkCache = new Dictionary<AssemblyMirror, SourceLinkMap []> ();
- SourceLinkMap [] GetSourceLinkMaps (MethodMirror method)
- {
- var asm = method.DeclaringType.Assembly;
- SourceLinkMap[] maps;
-
- lock (SourceLinkCache) {
- if (SourceLinkCache.TryGetValue (asm, out maps))
- return maps;
- }
-
- string jsonString = null;
-
- if (asm.VirtualMachine.Version.AtLeast (2, 48)) {
- jsonString = asm.ManifestModule.SourceLink;
- } else {
- // Read the pdb ourselves
- jsonString = GetPdbData (asm)?.GetSourceLinkBlob ();
- }
-
- if (!string.IsNullOrWhiteSpace(jsonString)) {
- try {
- var jsonSourceLink = JsonConvert.DeserializeObject<JsonSourceLink> (jsonString);
-
- if (jsonSourceLink != null && jsonSourceLink.Maps != null && jsonSourceLink.Maps.Any ())
- maps = jsonSourceLink.Maps.Select (kv => new SourceLinkMap (kv.Key, kv.Value)).ToArray ();
- } catch (JsonException ex) {
- DebuggerLoggingService.LogError ("Error reading source link", ex);
- }
- }
-
- lock (SourceLinkCache) {
- SourceLinkCache[asm] = maps ?? Array.Empty<SourceLinkMap> ();
- }
-
- return maps;
- }
-
- internal SourceLink GetSourceLink (MethodMirror method, string originalFileName)
- {
- if (originalFileName == null)
- return null;
-
- var maps = GetSourceLinkMaps (method);
- if (maps == null || maps.Length == 0)
- return null;
-
- originalFileName = originalFileName.Replace ('\\', '/');
- foreach (var map in maps) {
- var pattern = map.RelativePathWildcard.Replace ("*", "").Replace ('\\', '/');
-
- if (originalFileName.StartsWith (pattern, StringComparison.Ordinal)) {
- var localPath = originalFileName.Replace (pattern.Replace (".*", ""), "");
- var httpBasePath = map.UriWildcard.Replace ("*", "");
- // org/project-name/git-sha (usually)
- var pathAndQuery = new Uri (httpBasePath).GetComponents (UriComponents.Path, UriFormat.SafeUnescaped);
- // org/projectname/git-sha/path/to/file.cs
- var relativePath = Path.Combine (pathAndQuery, localPath);
- // Replace something like "f:/build/*" with "https://raw.githubusercontent.com/org/projectname/git-sha/*"
- var httpPath = Regex.Replace (originalFileName, pattern, httpBasePath);
- return new SourceLink (httpPath, relativePath);
- }
- }
- return null;
- }
-
- protected override Backtrace OnGetThreadBacktrace (long processId, long threadId)
- {
- return GetThreadBacktrace (GetThread (threadId));
- }
-
- protected override long OnGetElapsedTime (long processId, long threadId)
- {
- return GetElapsedTime (GetThread (threadId));
- }
-
- Backtrace GetThreadBacktrace (ThreadMirror thread)
- {
- return new Backtrace (new SoftDebuggerBacktrace (this, thread));
- }
-
- long GetElapsedTime (ThreadMirror thread)
- {
- return thread.ElapsedTime ();
- }
-
- string GetThreadName (ThreadMirror t)
- {
- string name = t.Name;
- if (string.IsNullOrEmpty (name)) {
- try {
- if (t.IsThreadPoolThread)
- return "<Thread Pool>";
- } catch {
- if (vm.Version.AtLeast (2, 2)) {
- throw;
- }
- return "<Thread>";
- }
- }
- return name;
- }
-
- protected override void OnFetchFrames (ThreadInfo [] threads)
- {
- var mirrorThreads = new List<ThreadMirror> (threads.Length);
- for (int i = 0; i < threads.Length; i++) {
- var thread = GetThread (threads [i].Id);
- if (thread != null) {
- mirrorThreads.Add (thread);
- }
- }
- ThreadMirror.FetchFrames (mirrorThreads);
- }
-
- protected override ThreadInfo[] OnGetThreads (long processId)
- {
- if (current_threads == null) {
- var mirrors = vm.GetThreads ();
- var threads = new ThreadInfo[mirrors.Count];
-
- for (int i = 0; i < mirrors.Count; i++) {
- var thread = mirrors[i];
-
- threads[i] = new ThreadInfo (processId, GetId (thread), GetThreadName (thread), null);
- }
-
- current_threads = threads;
- }
-
- return current_threads;
- }
-
- ThreadMirror GetThread (long threadId)
- {
- foreach (var thread in vm.GetThreads ()) {
- if (GetId (thread) == threadId)
- return thread;
- }
-
- return null;
- }
-
- ThreadInfo GetThread (ProcessInfo process, ThreadMirror thread)
- {
- long id = GetId (thread);
-
- foreach (var threadInfo in OnGetThreads (process.Id)) {
- if (threadInfo.Id == id)
- return threadInfo;
- }
-
- return null;
- }
-
- public override bool CanSetNextStatement {
- get { return vm.Version.AtLeast (2, 29); }
- }
-
- protected override void OnSetNextStatement (long threadId, string fileName, int line, int column)
- {
- if (!CanSetNextStatement)
- throw new NotSupportedException ();
-
- var thread = GetThread (threadId);
- if (thread == null)
- throw new ArgumentException ("Unknown thread.");
-
- var frames = thread.GetFrames ();
- if (frames.Length == 0)
- throw new NotSupportedException ();
-
- bool dummy = false;
- var location = FindLocationByMethod (frames[0].Method, fileName, line, column, ref dummy);
- if (location == null)
- throw new NotSupportedException ("Unable to set the next statement. The next statement cannot be set to another function.");
- try {
- thread.SetIP (location);
- currentAddress = location.ILOffset;
- currentStackDepth = frames.Length;
- } catch (ArgumentException) {
- throw new NotSupportedException ();
- }
- }
-
- protected override void OnSetNextStatement (long threadId, int ilOffset)
- {
- if (!CanSetNextStatement)
- throw new NotSupportedException ();
-
- var thread = GetThread (threadId);
- if (thread == null)
- throw new ArgumentException ("Unknown thread.");
-
- var frames = thread.GetFrames ();
- if (frames.Length == 0)
- throw new NotSupportedException ();
-
- var location = frames[0].Method.LocationAtILOffset (ilOffset);
- if (location == null)
- throw new NotSupportedException ();
-
- try {
- thread.SetIP (location);
- StackVersion++;
- RaiseStopEvent ();
- } catch (ArgumentException) {
- throw new NotSupportedException ();
- }
- }
-
- protected override BreakEventInfo OnInsertBreakEvent (BreakEvent breakEvent)
- {
- var breakInfo = new BreakInfo ();
-
- if (HasExited) {
- breakInfo.SetStatus (BreakEventStatus.Disconnected, null);
- return breakInfo;
- }
-
- if (breakEvent is FunctionBreakpoint function) {
- foreach (var method in FindMethodsByName (function.FunctionName, function.ParamTypes)) {
- if (!ResolveFunctionBreakpoint (breakInfo, function, method)) {
- breakInfo.SetStatus (BreakEventStatus.NotBound, null);
- }
- }
-
- // FIXME: handle types like GenericType<>, GenericType<SomeOtherType>, and GenericType<...>+NestedGenricType<...>
- var openParen = function.FunctionName.IndexOf ('(');
- int dot;
-
- if (openParen != -1) {
- //Handle stuff like SomeNamespace.SomeType.Method(SomeOtherNamespace.SomeOtherType)
- dot = function.FunctionName.LastIndexOf ('.', openParen);
- } else {
- dot = function.FunctionName.LastIndexOf ('.');
- }
- if (dot != -1)
- breakInfo.TypeName = function.FunctionName.Substring (0, dot);
-
- lock (pending_bes) {
- pending_bes.Add (breakInfo);
- }
- } else if (breakEvent is InstructionBreakpoint instruction) {
- Location location;
-
- breakInfo.FileName = instruction.FileName;
-
- if ((location = FindLocationByILOffset (instruction, instruction.FileName, out _, out var insideTypeRange)) != null) {
- breakInfo.Location = location;
- InsertBreakpoint (instruction, breakInfo);
- breakInfo.SetStatus (BreakEventStatus.Bound, null);
- } else if (insideTypeRange)
- breakInfo.SetStatus (BreakEventStatus.Invalid, null);
- else
- breakInfo.SetStatus (BreakEventStatus.NotBound, null);
-
- lock (pending_bes) {
- pending_bes.Add (breakInfo);
- }
- } else if (breakEvent is Breakpoint breakpoint) {
- bool insideLoadedRange;
- bool found = false;
-
- breakInfo.FileName = breakpoint.FileName;
-
- foreach (var location in FindLocationsByFile (breakpoint.FileName, breakpoint.Line, breakpoint.Column, out _, out insideLoadedRange)) {
- OnDebuggerOutput (false, string.Format ("Resolved pending breakpoint at '{0}:{1},{2}' to {3} [0x{4:x5}].\n",
- breakpoint.FileName, breakpoint.Line, breakpoint.Column,
- GetPrettyMethodName (location.Method), location.ILOffset));
-
- breakInfo.Location = location;
- InsertBreakpoint (breakpoint, breakInfo);
- found = true;
- breakInfo.SetStatus (BreakEventStatus.Bound, null);
- }
-
- lock (pending_bes) {
- pending_bes.Add (breakInfo);
- }
-
- if (!found) {
- if (insideLoadedRange)
- breakInfo.SetStatus (BreakEventStatus.Invalid, null);
- else
- breakInfo.SetStatus (BreakEventStatus.NotBound, null);
- }
- } else if (breakEvent is Catchpoint catchpoint) {
- if (!types.ContainsKey (catchpoint.ExceptionName)) {
- //
- // Same as in FindLocationByFile (), fetch types matching the type name
- if (vm.Version.AtLeast (2, 9)) {
- foreach (TypeMirror t in vm.GetTypes (catchpoint.ExceptionName, false))
- ProcessType (t);
- }
- }
-
- List<TypeMirror> typesList;
- if (types.TryGetValue (catchpoint.ExceptionName, out typesList)) {
- foreach (var type in typesList)
- InsertCatchpoint (catchpoint, breakInfo, type);
- breakInfo.SetStatus (BreakEventStatus.Bound, null);
- } else {
- breakInfo.SetStatus (BreakEventStatus.NotBound, null);
- }
-
- breakInfo.TypeName = catchpoint.ExceptionName;
- lock (pending_bes) {
- pending_bes.Add (breakInfo);
- }
- }
- UpdateTypeLoadFilters ();
- return breakInfo;
- }
-
- void UpdateTypeLoadFilters()
- {
- /*
- * TypeLoad events lead to too much wire traffic + suspend/resume work, so
- * filter them using the file names used by pending breakpoints.
- */
- if (vm.Version.AtLeast (2, 9)) {
- string [] sourceFileList;
- lock (pending_bes) {
- sourceFileList = pending_bes.Where (b => b.FileName != null).SelectMany ((b, i) => new [] {
- Path.GetFileName (b.FileName),
- b.FileName
- }).Distinct ().ToArray ();
- }
- if (sourceFileList.Length > 0) {
- //HACK: with older versions of sdb that don't support case-insenitive compares,
- //explicitly try lowercased drivename on windows, since csc (when not hosted in VS) lowercases
- //the drivename in the pdb files that get converted to mdbs as-is
- if (IsWindows && !vm.Version.AtLeast (2, 12)) {
- int originalCount = sourceFileList.Length;
- Array.Resize (ref sourceFileList, originalCount * 2);
- for (int i = 0; i < originalCount; i++) {
- string n = sourceFileList[i];
- sourceFileList[originalCount + i] = char.ToLower (n[0]) + n.Substring (1);
- }
- }
-
- if (typeLoadReq == null) {
- typeLoadReq = vm.CreateTypeLoadRequest ();
- }
- typeLoadReq.Enabled = false;
- typeLoadReq.SourceFileFilter = sourceFileList;
- typeLoadReq.Enabled = true;
- }
-
- string [] typeNameList;
- lock (pending_bes) {
- typeNameList = pending_bes.Where (b => b.TypeName != null).Select (b => b.TypeName).ToArray ();
- }
- if (typeNameList.Length > 0) {
- // Use a separate request since the filters are ANDed together
- if (typeLoadTypeNameReq == null) {
- typeLoadTypeNameReq = vm.CreateTypeLoadRequest ();
- }
- typeLoadTypeNameReq.Enabled = false;
- typeLoadTypeNameReq.TypeNameFilter = typeNameList;
- typeLoadTypeNameReq.Enabled = true;
- }
- }
- }
-
- private Location FindLocationByILOffset (InstructionBreakpoint bp, string filename, out bool isGeneric, out bool insideTypeRange)
- {
- var typesInFile = new List<TypeMirror> ();
-
- AddFileToSourceMapping (filename);
-
- insideTypeRange = true;
- isGeneric = false;
-
- lock (source_to_type) {
- if (source_to_type.TryGetValue (filename, out typesInFile)) {
- foreach (var type in typesInFile) {
- var method = type.GetMethod (bp.MethodName);
- if (method != null) {
- foreach (var location in method.Locations) {
- if (location.ILOffset == bp.ILOffset) {
- isGeneric = type.IsGenericType;
- return location;
- }
- }
- }
- }
- }
- }
-
- return null;
- }
-
- protected override void OnRemoveBreakEvent (BreakEventInfo eventInfo)
- {
- if (HasExited)
- return;
-
- var bi = (BreakInfo) eventInfo;
- if (bi.Requests.Count != 0) {
- foreach (var request in bi.Requests.Keys) {
- request.Enabled = false;
- breakpoints.Remove (request);
- }
-
- RemoveQueuedBreakEvents (bi.Requests);
- }
-
- lock (pending_bes) {
- pending_bes.Remove (bi);
- }
- }
-
- protected override void OnEnableBreakEvent (BreakEventInfo eventInfo, bool enable)
- {
- if (HasExited)
- return;
-
- var bi = (BreakInfo) eventInfo;
- if (bi.Requests.Count != 0) {
- foreach (var request in bi.Requests.Keys)
- request.Enabled = enable;
-
- if (!enable)
- RemoveQueuedBreakEvents (bi.Requests);
- }
- }
-
- protected override void OnUpdateBreakEvent (BreakEventInfo eventInfo)
- {
- }
-
- void InsertBreakpoint (Breakpoint bp, BreakInfo bi)
- {
- InsertBreakpoint (bp, bi, bi.Location.Method, bi.Location.ILOffset);
- }
-
+ return pdbBlob != null ? new PortablePdbData (pdbBlob) : null;
+ }
+
+ internal PortablePdbData GetPdbData (MethodMirror method)
+ {
+ return GetPdbData (method.DeclaringType.Assembly);
+ }
+
+ readonly Dictionary<AssemblyMirror, SourceLinkMap []> SourceLinkCache = new Dictionary<AssemblyMirror, SourceLinkMap []> ();
+ SourceLinkMap [] GetSourceLinkMaps (MethodMirror method)
+ {
+ var asm = method.DeclaringType.Assembly;
+ SourceLinkMap[] maps;
+
+ lock (SourceLinkCache) {
+ if (SourceLinkCache.TryGetValue (asm, out maps))
+ return maps;
+ }
+
+ string jsonString = null;
+
+ if (asm.VirtualMachine.Version.AtLeast (2, 48)) {
+ jsonString = asm.ManifestModule.SourceLink;
+ } else {
+ // Read the pdb ourselves
+ jsonString = GetPdbData (asm)?.GetSourceLinkBlob ();
+ }
+
+ if (!string.IsNullOrWhiteSpace(jsonString)) {
+ try {
+ var jsonSourceLink = JsonConvert.DeserializeObject<JsonSourceLink> (jsonString);
+
+ if (jsonSourceLink != null && jsonSourceLink.Maps != null && jsonSourceLink.Maps.Any ())
+ maps = jsonSourceLink.Maps.Select (kv => new SourceLinkMap (kv.Key, kv.Value)).ToArray ();
+ } catch (JsonException ex) {
+ DebuggerLoggingService.LogError ("Error reading source link", ex);
+ }
+ }
+
+ lock (SourceLinkCache) {
+ SourceLinkCache[asm] = maps ?? Array.Empty<SourceLinkMap> ();
+ }
+
+ return maps;
+ }
+
+ internal SourceLink GetSourceLink (MethodMirror method, string originalFileName)
+ {
+ if (originalFileName == null)
+ return null;
+
+ var maps = GetSourceLinkMaps (method);
+ if (maps == null || maps.Length == 0)
+ return null;
+
+ originalFileName = originalFileName.Replace ('\\', '/');
+ foreach (var map in maps) {
+ var pattern = map.RelativePathWildcard.Replace ("*", "").Replace ('\\', '/');
+
+ if (originalFileName.StartsWith (pattern, StringComparison.Ordinal)) {
+ var localPath = originalFileName.Replace (pattern.Replace (".*", ""), "");
+ var httpBasePath = map.UriWildcard.Replace ("*", "");
+ // org/project-name/git-sha (usually)
+ var pathAndQuery = new Uri (httpBasePath).GetComponents (UriComponents.Path, UriFormat.SafeUnescaped);
+ // org/projectname/git-sha/path/to/file.cs
+ var relativePath = Path.Combine (pathAndQuery, localPath);
+ // Replace something like "f:/build/*" with "https://raw.githubusercontent.com/org/projectname/git-sha/*"
+ var httpPath = Regex.Replace (originalFileName, pattern, httpBasePath);
+ return new SourceLink (httpPath, relativePath);
+ }
+ }
+ return null;
+ }
+
+ protected override Backtrace OnGetThreadBacktrace (long processId, long threadId)
+ {
+ return GetThreadBacktrace (GetThread (threadId));
+ }
+
+ protected override long OnGetElapsedTime (long processId, long threadId)
+ {
+ return GetElapsedTime (GetThread (threadId));
+ }
+
+ Backtrace GetThreadBacktrace (ThreadMirror thread)
+ {
+ return new Backtrace (new SoftDebuggerBacktrace (this, thread));
+ }
+
+ long GetElapsedTime (ThreadMirror thread)
+ {
+ return thread.ElapsedTime ();
+ }
+
+ string GetThreadName (ThreadMirror t)
+ {
+ string name = t.Name;
+ if (string.IsNullOrEmpty (name)) {
+ try {
+ if (t.IsThreadPoolThread)
+ return "<Thread Pool>";
+ } catch {
+ if (vm.Version.AtLeast (2, 2)) {
+ throw;
+ }
+ return "<Thread>";
+ }
+ }
+ return name;
+ }
+
+ protected override void OnFetchFrames (ThreadInfo [] threads)
+ {
+ var mirrorThreads = new List<ThreadMirror> (threads.Length);
+ for (int i = 0; i < threads.Length; i++) {
+ var thread = GetThread (threads [i].Id);
+ if (thread != null) {
+ mirrorThreads.Add (thread);
+ }
+ }
+ ThreadMirror.FetchFrames (mirrorThreads);
+ }
+
+ protected override ThreadInfo[] OnGetThreads (long processId)
+ {
+ if (current_threads == null) {
+ var mirrors = vm.GetThreads ();
+ var threads = new ThreadInfo[mirrors.Count];
+
+ for (int i = 0; i < mirrors.Count; i++) {
+ var thread = mirrors[i];
+
+ threads[i] = new ThreadInfo (processId, GetId (thread), GetThreadName (thread), null);
+ }
+
+ current_threads = threads;
+ }
+
+ return current_threads;
+ }
+
+ ThreadMirror GetThread (long threadId)
+ {
+ foreach (var thread in vm.GetThreads ()) {
+ if (GetId (thread) == threadId)
+ return thread;
+ }
+
+ return null;
+ }
+
+ ThreadInfo GetThread (ProcessInfo process, ThreadMirror thread)
+ {
+ long id = GetId (thread);
+
+ foreach (var threadInfo in OnGetThreads (process.Id)) {
+ if (threadInfo.Id == id)
+ return threadInfo;
+ }
+
+ return null;
+ }
+
+ public override bool CanSetNextStatement {
+ get { return vm.Version.AtLeast (2, 29); }
+ }
+
+ protected override void OnSetNextStatement (long threadId, string fileName, int line, int column)
+ {
+ if (!CanSetNextStatement)
+ throw new NotSupportedException ();
+
+ var thread = GetThread (threadId);
+ if (thread == null)
+ throw new ArgumentException ("Unknown thread.");
+
+ var frames = thread.GetFrames ();
+ if (frames.Length == 0)
+ throw new NotSupportedException ();
+
+ bool dummy = false;
+ var location = FindLocationByMethod (frames[0].Method, fileName, line, column, ref dummy);
+ if (location == null)
+ throw new NotSupportedException ("Unable to set the next statement. The next statement cannot be set to another function.");
+ try {
+ thread.SetIP (location);
+ currentAddress = location.ILOffset;
+ currentStackDepth = frames.Length;
+ } catch (ArgumentException) {
+ throw new NotSupportedException ();
+ }
+ }
+
+ protected override void OnSetNextStatement (long threadId, int ilOffset)
+ {
+ if (!CanSetNextStatement)
+ throw new NotSupportedException ();
+
+ var thread = GetThread (threadId);
+ if (thread == null)
+ throw new ArgumentException ("Unknown thread.");
+
+ var frames = thread.GetFrames ();
+ if (frames.Length == 0)
+ throw new NotSupportedException ();
+
+ var location = frames[0].Method.LocationAtILOffset (ilOffset);
+ if (location == null)
+ throw new NotSupportedException ();
+
+ try {
+ thread.SetIP (location);
+ StackVersion++;
+ RaiseStopEvent ();
+ } catch (ArgumentException) {
+ throw new NotSupportedException ();
+ }
+ }
+
+ protected override BreakEventInfo OnInsertBreakEvent (BreakEvent breakEvent)
+ {
+ var breakInfo = new BreakInfo ();
+
+ if (HasExited) {
+ breakInfo.SetStatus (BreakEventStatus.Disconnected, null);
+ return breakInfo;
+ }
+
+ if (breakEvent is FunctionBreakpoint function) {
+ foreach (var method in FindMethodsByName (function.FunctionName, function.ParamTypes)) {
+ if (!ResolveFunctionBreakpoint (breakInfo, function, method)) {
+ breakInfo.SetStatus (BreakEventStatus.NotBound, null);
+ }
+ }
+
+ // FIXME: handle types like GenericType<>, GenericType<SomeOtherType>, and GenericType<...>+NestedGenricType<...>
+ var openParen = function.FunctionName.IndexOf ('(');
+ int dot;
+
+ if (openParen != -1) {
+ //Handle stuff like SomeNamespace.SomeType.Method(SomeOtherNamespace.SomeOtherType)
+ dot = function.FunctionName.LastIndexOf ('.', openParen);
+ } else {
+ dot = function.FunctionName.LastIndexOf ('.');
+ }
+ if (dot != -1)
+ breakInfo.TypeName = function.FunctionName.Substring (0, dot);
+
+ lock (pending_bes) {
+ pending_bes.Add (breakInfo);
+ }
+ } else if (breakEvent is InstructionBreakpoint instruction) {
+ Location location;
+
+ breakInfo.FileName = instruction.FileName;
+
+ if ((location = FindLocationByILOffset (instruction, instruction.FileName, out _, out var insideTypeRange)) != null) {
+ breakInfo.Location = location;
+ InsertBreakpoint (instruction, breakInfo);
+ breakInfo.SetStatus (BreakEventStatus.Bound, null);
+ } else if (insideTypeRange)
+ breakInfo.SetStatus (BreakEventStatus.Invalid, null);
+ else
+ breakInfo.SetStatus (BreakEventStatus.NotBound, null);
+
+ lock (pending_bes) {
+ pending_bes.Add (breakInfo);
+ }
+ } else if (breakEvent is Breakpoint breakpoint) {
+ bool insideLoadedRange;
+ bool found = false;
+
+ breakInfo.FileName = breakpoint.FileName;
+
+ foreach (var location in FindLocationsByFile (breakpoint.FileName, breakpoint.Line, breakpoint.Column, out _, out insideLoadedRange)) {
+ OnDebuggerOutput (false, string.Format ("Resolved pending breakpoint at '{0}:{1},{2}' to {3} [0x{4:x5}].\n",
+ breakpoint.FileName, breakpoint.Line, breakpoint.Column,
+ GetPrettyMethodName (location.Method), location.ILOffset));
+
+ breakInfo.Location = location;
+ InsertBreakpoint (breakpoint, breakInfo);
+ found = true;
+ breakInfo.SetStatus (BreakEventStatus.Bound, null);
+ }
+ lock (pending_bes) {
+ pending_bes.Add (breakInfo);
+ }
+
+ if (!found) {
+ breakInfo.Breakpoint = breakpoint;
+ if (insideLoadedRange)
+ breakInfo.SetStatus (BreakEventStatus.Invalid, null);
+ else
+ breakInfo.SetStatus (BreakEventStatus.NotBound, null);
+ }
+ } else if (breakEvent is Catchpoint catchpoint) {
+ if (!types.ContainsKey (catchpoint.ExceptionName)) {
+ //
+ // Same as in FindLocationByFile (), fetch types matching the type name
+ if (vm.Version.AtLeast (2, 9)) {
+ foreach (TypeMirror t in vm.GetTypes (catchpoint.ExceptionName, false))
+ ProcessType (t);
+ }
+ }
+
+ List<TypeMirror> typesList;
+ if (types.TryGetValue (catchpoint.ExceptionName, out typesList)) {
+ foreach (var type in typesList)
+ InsertCatchpoint (catchpoint, breakInfo, type);
+ breakInfo.SetStatus (BreakEventStatus.Bound, null);
+ } else {
+ breakInfo.SetStatus (BreakEventStatus.NotBound, null);
+ }
+
+ breakInfo.TypeName = catchpoint.ExceptionName;
+ lock (pending_bes) {
+ pending_bes.Add (breakInfo);
+ }
+ }
+ UpdateTypeLoadFilters ();
+ return breakInfo;
+ }
+
+ void UpdateTypeLoadFilters()
+ {
+ /*
+ * TypeLoad events lead to too much wire traffic + suspend/resume work, so
+ * filter them using the file names used by pending breakpoints.
+ */
+ if (vm.Version.AtLeast (2, 9)) {
+ string [] sourceFileList;
+ lock (pending_bes) {
+ sourceFileList = pending_bes.Where (b => b.FileName != null).SelectMany ((b, i) => new [] {
+ Path.GetFileName (b.FileName),
+ b.FileName
+ }).Distinct ().ToArray ();
+ }
+ if (sourceFileList.Length > 0) {
+ //HACK: with older versions of sdb that don't support case-insenitive compares,
+ //explicitly try lowercased drivename on windows, since csc (when not hosted in VS) lowercases
+ //the drivename in the pdb files that get converted to mdbs as-is
+ if (IsWindows && !vm.Version.AtLeast (2, 12)) {
+ int originalCount = sourceFileList.Length;
+ Array.Resize (ref sourceFileList, originalCount * 2);
+ for (int i = 0; i < originalCount; i++) {
+ string n = sourceFileList[i];
+ sourceFileList[originalCount + i] = char.ToLower (n[0]) + n.Substring (1);
+ }
+ }
+
+ if (typeLoadReq == null) {
+ typeLoadReq = vm.CreateTypeLoadRequest ();
+ }
+ typeLoadReq.Enabled = false;
+ typeLoadReq.SourceFileFilter = sourceFileList;
+ typeLoadReq.Enabled = true;
+ }
+
+ string [] typeNameList;
+ lock (pending_bes) {
+ typeNameList = pending_bes.Where (b => b.TypeName != null).Select (b => b.TypeName).ToArray ();
+ }
+ if (typeNameList.Length > 0) {
+ // Use a separate request since the filters are ANDed together
+ if (typeLoadTypeNameReq == null) {
+ typeLoadTypeNameReq = vm.CreateTypeLoadRequest ();
+ }
+ typeLoadTypeNameReq.Enabled = false;
+ typeLoadTypeNameReq.TypeNameFilter = typeNameList;
+ typeLoadTypeNameReq.Enabled = true;
+ }
+ }
+ }
+
+ private Location FindLocationByILOffset (InstructionBreakpoint bp, string filename, out bool isGeneric, out bool insideTypeRange)
+ {
+ var typesInFile = new List<TypeMirror> ();
+
+ AddFileToSourceMapping (filename);
+
+ insideTypeRange = true;
+ isGeneric = false;
+
+ lock (source_to_type) {
+ if (source_to_type.TryGetValue (filename, out typesInFile)) {
+ foreach (var type in typesInFile) {
+ var method = type.GetMethod (bp.MethodName);
+ if (method != null) {
+ foreach (var location in method.Locations) {
+ if (location.ILOffset == bp.ILOffset) {
+ isGeneric = type.IsGenericType;
+ return location;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ protected override void OnRemoveBreakEvent (BreakEventInfo eventInfo)
+ {
+ if (HasExited)
+ return;
+
+ var bi = (BreakInfo) eventInfo;
+ if (bi.Requests.Count != 0) {
+ foreach (var request in bi.Requests.Keys) {
+ request.Enabled = false;
+ breakpoints.Remove (request);
+ }
+
+ RemoveQueuedBreakEvents (bi.Requests);
+ }
+
+ lock (pending_bes) {
+ pending_bes.Remove (bi);
+ }
+ }
+
+ protected override void OnEnableBreakEvent (BreakEventInfo eventInfo, bool enable)
+ {
+ if (HasExited)
+ return;
+
+ var bi = (BreakInfo) eventInfo;
+ if (bi.Requests.Count != 0) {
+ foreach (var request in bi.Requests.Keys)
+ request.Enabled = enable;
+
+ if (!enable)
+ RemoveQueuedBreakEvents (bi.Requests);
+ }
+ }
+
+ protected override void OnUpdateBreakEvent (BreakEventInfo eventInfo)
+ {
+ }
+
+ void InsertBreakpoint (Breakpoint bp, BreakInfo bi)
+ {
+ InsertBreakpoint (bp, bi, bi.Location.Method, bi.Location.ILOffset);
+ }
+
void UpdateBreakpoint (Breakpoint bp, BreakInfo bi)
{
foreach (var req in bi.Requests)
@@ -1364,73 +1367,73 @@ namespace Mono.Debugging.Soft
req.Key.UpdateReqId (request.GetId());
req.Key.Enabled = bp.Enabled;
}
- }
-
- void InsertBreakpoint (Breakpoint bp, BreakInfo bi, MethodMirror method, int ilOffset)
- {
- EventRequest request;
-
- request = vm.SetBreakpoint (method, ilOffset);
- request.Enabled = bp.Enabled;
- bi.Requests.Add (request, method.DeclaringType.Assembly);
-
- breakpoints [request] = bi;
-
- if (bi.Location != null && (bi.Location.LineNumber != bp.Line || bi.Location.ColumnNumber != bp.Column))
- bi.AdjustBreakpointLocation (bi.Location.LineNumber, bi.Location.ColumnNumber);
- }
-
- void InsertCatchpoint (Catchpoint cp, BreakInfo bi, TypeMirror excType)
- {
- ExceptionEventRequest request;
-
+ }
+
+ void InsertBreakpoint (Breakpoint bp, BreakInfo bi, MethodMirror method, int ilOffset)
+ {
+ EventRequest request;
+
+ request = vm.SetBreakpoint (method, ilOffset);
+ request.Enabled = bp.Enabled;
+ bi.Requests.Add (request, method.DeclaringType.Assembly);
+
+ breakpoints [request] = bi;
+
+ if (bi.Location != null && (bi.Location.LineNumber != bp.Line || bi.Location.ColumnNumber != bp.Column))
+ bi.AdjustBreakpointLocation (bi.Location.LineNumber, bi.Location.ColumnNumber);
+ }
+
+ void InsertCatchpoint (Catchpoint cp, BreakInfo bi, TypeMirror excType)
+ {
+ ExceptionEventRequest request;
+
request = vm.CreateExceptionRequest (excType, true, true);
//Commenting Count so we have better control of counting
//because VM only allows count equal to some number but we need also
//lower, greater, equal or greater...
//Plus some day we might want to put filtering before counting...
//request.Count = cp.HitCount; // Note: need to set HitCount *before* enabling
- if (vm.Version.AtLeast (2, 25))
- request.IncludeSubclasses = cp.IncludeSubclasses; // Note: need to set IncludeSubclasses *before* enabling
- request.Enabled = cp.Enabled;
- bi.Requests.Add (request, excType.Assembly);
-
- breakpoints[request] = bi;
+ if (vm.Version.AtLeast (2, 25))
+ request.IncludeSubclasses = cp.IncludeSubclasses; // Note: need to set IncludeSubclasses *before* enabling
+ request.Enabled = cp.Enabled;
+ bi.Requests.Add (request, excType.Assembly);
+
+ breakpoints[request] = bi;
}
- #region ExceptionRequest
+ #region ExceptionRequest
//VS in Windows doesn't have the concept of a Catchpoint. In the past we did use it anyway, but that leads to all sort of issues and bugs.
//The most important difference is that subclasses are never caught, and that Others is a concept that the Catchpoint doesn't support properly.
//This region is for Windows, to be able to properly handle exceptions with an exception list. The concept of Other is supported starting with vm version 2.54.
- readonly Dictionary<string, ExceptionEventRequest> exceptionRequests = new Dictionary<string, ExceptionEventRequest>();
- ExceptionEventRequest otherExceptions;
-
+ readonly Dictionary<string, ExceptionEventRequest> exceptionRequests = new Dictionary<string, ExceptionEventRequest>();
+ ExceptionEventRequest otherExceptions;
+
public void EnableException(string exceptionType, bool caught = true)
{
lock (exceptionRequests) {
if (!types.ContainsKey (exceptionType)) {
- if (vm.Version.AtLeast (2, 9)) {
- try {
- foreach (TypeMirror t in vm.GetTypes (exceptionType, false))
- ProcessType (t);
- }
- catch (CommandException exc) {
+ if (vm.Version.AtLeast (2, 9)) {
+ try {
+ foreach (TypeMirror t in vm.GetTypes (exceptionType, false))
+ ProcessType (t);
+ }
+ catch (CommandException exc) {
if (outputOptions.ExceptionMessage)
OnDebuggerOutput (false, string.Format ("Error while parsing type ‘{0}’.\n", exceptionType));
- }
- }
- }
+ }
+ }
+ }
- if (types.TryGetValue(exceptionType, out var typemirrors)) {
+ if (types.TryGetValue(exceptionType, out var typemirrors)) {
var exception = typemirrors.First();
var request = vm.CreateExceptionRequest (exception, caught, true, false);
- request.Enable();
- exceptionRequests[exceptionType] = request;
- }
- }
- }
-
+ request.Enable();
+ exceptionRequests[exceptionType] = request;
+ }
+ }
+ }
+
public void EnableOtherExceptions()
{
if (!vm.Version.AtLeast(2, 54))
@@ -1439,8 +1442,8 @@ namespace Mono.Debugging.Soft
if (otherExceptions == null)
otherExceptions = vm.CreateExceptionRequest (null, true, true, true);
otherExceptions.Enable ();
- }
-
+ }
+
public void DisableException(string exceptionType, bool setuncaught = true)
{
lock (exceptionRequests) {
@@ -1451,320 +1454,320 @@ namespace Mono.Debugging.Soft
if (setuncaught)
EnableException (exceptionType, false);
}
- }
-
+ }
+
public void DisableOtherExceptions()
{
if (otherExceptions != null) {
otherExceptions.Disable();
}
- }
-
+ }
+
public void RemoveException(string exceptionType)
{
DisableException(exceptionType, false);
}
- #endregion
-
- static bool CheckTypeName (string typeName, string name)
- {
- // if the name provided is empty, it matches anything.
- if (string.IsNullOrEmpty (name))
- return true;
-
- if (name.StartsWith ("global::", StringComparison.Ordinal)) {
- if (typeName != name.Substring ("global::".Length))
- return false;
- } else if (name.StartsWith ("::", StringComparison.Ordinal)) {
- if (typeName != name.Substring ("::".Length))
- return false;
- } else {
- // be a little more flexible with what we match... i.e. "Console" should match "System.Console"
- if (typeName != null && typeName.Length > name.Length) {
- if (!typeName.EndsWith (name, StringComparison.Ordinal))
- return false;
-
- char delim = typeName[typeName.Length - name.Length];
- if (delim != '.' && delim != '+')
- return false;
- } else if (typeName != name) {
- return false;
- }
- }
-
- return true;
- }
-
- static bool CheckTypeName (TypeMirror type, string name)
- {
- if (string.IsNullOrEmpty (name)) {
- // empty name matches anything
- return true;
- }
-
- if (name[name.Length - 1] == '?') {
- // canonicalize the user-specified nullable type
- return CheckTypeName (type, string.Format ("System.Nullable<{0}>", name.Substring (0, name.Length - 1)));
- }
-
- if (type.IsArray) {
- int startIndex = name.LastIndexOf ('[');
- int endIndex = name.Length - 1;
-
- if (startIndex == -1 || name[endIndex] != ']') {
- // the user-specified type is not an array
- return false;
- }
-
- var rank = name.Substring (startIndex + 1, endIndex - (startIndex + 1)).Split (new [] { ',' });
- if (rank.Length != type.GetArrayRank ())
- return false;
-
- return CheckTypeName (type.GetElementType (), name.Substring (0, startIndex).TrimEnd ());
- }
-
- if (type.IsPointer) {
- if (name.Length < 2 || name[name.Length - 1] != '*')
- return false;
-
- return CheckTypeName (type.GetElementType (), name.Substring (0, name.Length - 1).TrimEnd ());
- }
-
- if (type.IsGenericType) {
- int startIndex = name.IndexOf ('<');
- int endIndex = name.Length - 1;
-
- if (startIndex == -1 || name[endIndex] != '>') {
- // the user-specified type is not a generic type
- return false;
- }
-
- // make sure that the type name matches (minus generics)
- string subName = name.Substring (0, startIndex);
- string typeName = type.FullName;
- int tick;
-
- if ((tick = typeName.IndexOf ('`')) != -1)
- typeName = typeName.Substring (0, tick);
-
- if (!CheckTypeName (typeName, subName))
- return false;
-
- string[] paramTypes;
- if (!FunctionBreakpoint.TryParseParameters (name, startIndex + 1, endIndex, out paramTypes))
- return false;
-
- TypeMirror[] argTypes = type.GetGenericArguments ();
- if (paramTypes.Length != argTypes.Length)
- return false;
-
- for (int i = 0; i < paramTypes.Length; i++) {
- if (!CheckTypeName (argTypes[i], paramTypes[i]))
- return false;
- }
- } else if (!CheckTypeName (type.CSharpName, name)) {
- if (!CheckTypeName (type.FullName, name))
- return false;
- }
-
- return true;
- }
-
- static bool CheckMethodParams (MethodMirror method, string[] paramTypes)
- {
- if (paramTypes == null) {
- // User supplied no params to match against, match anything we find.
- return true;
- }
-
- var parameters = method.GetParameters ();
- if (parameters.Length != paramTypes.Length)
- return false;
-
- for (int i = 0; i < paramTypes.Length; i++) {
- if (!CheckTypeName (parameters[i].ParameterType, paramTypes[i]))
- return false;
- }
-
- return true;
- }
-
- bool IsGenericMethod (MethodMirror method)
- {
- return vm.Version.AtLeast (2, 12) && method.IsGenericMethod;
- }
-
- //If paramType == null all overloads are returned
- IEnumerable<MethodMirror> FindMethodsByName (string function, string[] paramTypes)
- {
- if (!started)
- yield break;
-
- if (vm.Version.AtLeast (2, 9)) {
- var bracket = function.IndexOf ('(');
- int dot;
- if (bracket != -1) {
- //Handle stuff like SomeNamespace.SomeType.Method(SomeOtherNamespace.SomeOtherType)
- dot = function.LastIndexOf ('.', bracket);
- } else {
- dot = function.LastIndexOf ('.');
- }
- if (dot == -1 || dot + 1 == function.Length)
- yield break;
-
- // FIXME: handle types like GenericType<>, GenericType<SomeOtherType>, and GenericType<...>+NestedGenricType<...>
- string methodName;
- string typeName = function.Substring (0, dot);
- if (bracket == -1) {
- methodName = function.Substring (dot + 1);
- } else {
- methodName = function.Substring (dot + 1, bracket - (dot + 1));
- }
-
- // FIXME: need a way of querying all types so we can substring match typeName (e.g. user may have typed "Console" instead of "System.Console")
- foreach (var type in vm.GetTypes (typeName, false)) {
- ProcessType (type);
-
- foreach (var method in type.GetMethodsByNameFlags (methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static, false)) {
- if (!CheckMethodParams (method, paramTypes))
- continue;
-
- yield return method;
- }
- }
- }
-
- yield break;
- }
-
- IList<Location> FindLocationsByFile (string file, int line, int column, out bool genericTypeOrMethod, out bool insideLoadedRange)
- {
- var locations = new List<Location> ();
-
- genericTypeOrMethod = false;
- insideLoadedRange = false;
-
- if (!started)
- return locations;
-
- var filename = Path.GetFileName (file);
-
- AddFileToSourceMapping (filename);
-
- lock (source_to_type) {
- // Try already loaded types in the current source file
- if (source_to_type.TryGetValue (filename, out var mirrors)) {
- foreach (var type in mirrors) {
- var location = FindLocationByType (type, file, line, column, out var genericMethod, out var insideRange);
- if (insideRange)
- insideLoadedRange = true;
-
- if (location != null) {
- if (genericMethod || type.IsGenericType)
- genericTypeOrMethod = true;
-
- locations.Add (location);
- }
- }
- }
- }
-
- return locations;
- }
-
- private void AddFileToSourceMapping (string filename)
- {
- //
- // Fetch types matching the source file from the debuggee, and add them
- // to the source file->type mapping tables.
- // This is needed because we don't receive type load events for all types,
- // just the ones which match a source file with an existing breakpoint.
- //
- if (vm.Version.AtLeast (2, 9)) {
- IList<TypeMirror> typesInFile;
- if (vm.Version.AtLeast (2, 12)) {
- typesInFile = vm.GetTypesForSourceFile (filename, IgnoreFilenameCase);
- } else {
- typesInFile = vm.GetTypesForSourceFile (filename, false);
- //HACK: with older versions of sdb that don't support case-insenitive compares,
- //explicitly try lowercased drivename on windows, since csc (when not hosted in VS) lowercases
- //the drivename in the pdb files that get converted to mdbs as-is
- if (typesInFile.Count == 0 && IsWindows) {
- string alternateCaseFilename = char.ToLower (filename [0]) + filename.Substring (1);
- typesInFile = vm.GetTypesForSourceFile (alternateCaseFilename, false);
- }
- }
-
- foreach (TypeMirror t in typesInFile)
- ProcessType (t);
- }
- }
-
- public override bool CanCancelAsyncEvaluations
- {
- get
- {
- return Adaptor.IsEvaluating;
- }
- }
-
- protected override void OnCancelAsyncEvaluations ()
- {
- Adaptor.CancelAsyncOperations ();
- }
-
- protected override void OnNextInstruction ()
- {
- Step (StepDepth.Over, StepSize.Min);
- }
-
- protected override void OnNextLine ()
- {
- Step (StepDepth.Over, StepSize.Line);
- }
-
- void Step (StepDepth depth, StepSize size)
- {
- try {
- Adaptor.CancelAsyncOperations (); // This call can block, so it has to run in background thread to avoid keeping the main session lock
- var req = vm.CreateStepRequest (current_thread);
- req.Depth = depth;
- req.Size = size;
- req.Filter = ShouldFilterStaticCtor () | StepFilter.DebuggerHidden | StepFilter.DebuggerStepThrough;
- if (Options.ProjectAssembliesOnly)
- req.Filter |= StepFilter.DebuggerNonUserCode;
- if (assemblyFilters != null && assemblyFilters.Count > 0)
- req.AssemblyFilter = assemblyFilters;
- try {
- req.Enabled = true;
- } catch (NotSupportedException e) {
- if (vm.Version.AtLeast (2, 19)) //catch NotSupportedException thrown by old version of protocol
- throw e;
- }
- currentStepRequest = req;
- OnResumed ();
- vm.Resume ();
- DequeueEventsForFirstThread ();
- } catch (CommandException ex) {
- string reason;
-
- switch (ex.ErrorCode) {
- case ErrorCode.INVALID_FRAMEID: reason = "invalid frame id"; break;
- case ErrorCode.NOT_SUSPENDED: reason = "VM not suspended"; break;
- case ErrorCode.ERR_UNLOADED: reason = "AppDomain has been unloaded"; break;
- case ErrorCode.NO_SEQ_POINT_AT_IL_OFFSET: reason = "no sequence point at the specified IL offset"; break;
- default: reason = ex.ErrorCode.ToString (); break;
- }
-
- if (outputOptions.ExceptionMessage)
- OnDebuggerOutput (true, string.Format ("Step request failed: {0}.", reason));
- DebuggerLoggingService.LogError ("Step request failed", ex);
- } catch (Exception ex) {
+ #endregion
+
+ static bool CheckTypeName (string typeName, string name)
+ {
+ // if the name provided is empty, it matches anything.
+ if (string.IsNullOrEmpty (name))
+ return true;
+
+ if (name.StartsWith ("global::", StringComparison.Ordinal)) {
+ if (typeName != name.Substring ("global::".Length))
+ return false;
+ } else if (name.StartsWith ("::", StringComparison.Ordinal)) {
+ if (typeName != name.Substring ("::".Length))
+ return false;
+ } else {
+ // be a little more flexible with what we match... i.e. "Console" should match "System.Console"
+ if (typeName != null && typeName.Length > name.Length) {
+ if (!typeName.EndsWith (name, StringComparison.Ordinal))
+ return false;
+
+ char delim = typeName[typeName.Length - name.Length];
+ if (delim != '.' && delim != '+')
+ return false;
+ } else if (typeName != name) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ static bool CheckTypeName (TypeMirror type, string name)
+ {
+ if (string.IsNullOrEmpty (name)) {
+ // empty name matches anything
+ return true;
+ }
+
+ if (name[name.Length - 1] == '?') {
+ // canonicalize the user-specified nullable type
+ return CheckTypeName (type, string.Format ("System.Nullable<{0}>", name.Substring (0, name.Length - 1)));
+ }
+
+ if (type.IsArray) {
+ int startIndex = name.LastIndexOf ('[');
+ int endIndex = name.Length - 1;
+
+ if (startIndex == -1 || name[endIndex] != ']') {
+ // the user-specified type is not an array
+ return false;
+ }
+
+ var rank = name.Substring (startIndex + 1, endIndex - (startIndex + 1)).Split (new [] { ',' });
+ if (rank.Length != type.GetArrayRank ())
+ return false;
+
+ return CheckTypeName (type.GetElementType (), name.Substring (0, startIndex).TrimEnd ());
+ }
+
+ if (type.IsPointer) {
+ if (name.Length < 2 || name[name.Length - 1] != '*')
+ return false;
+
+ return CheckTypeName (type.GetElementType (), name.Substring (0, name.Length - 1).TrimEnd ());
+ }
+
+ if (type.IsGenericType) {
+ int startIndex = name.IndexOf ('<');
+ int endIndex = name.Length - 1;
+
+ if (startIndex == -1 || name[endIndex] != '>') {
+ // the user-specified type is not a generic type
+ return false;
+ }
+
+ // make sure that the type name matches (minus generics)
+ string subName = name.Substring (0, startIndex);
+ string typeName = type.FullName;
+ int tick;
+
+ if ((tick = typeName.IndexOf ('`')) != -1)
+ typeName = typeName.Substring (0, tick);
+
+ if (!CheckTypeName (typeName, subName))
+ return false;
+
+ string[] paramTypes;
+ if (!FunctionBreakpoint.TryParseParameters (name, startIndex + 1, endIndex, out paramTypes))
+ return false;
+
+ TypeMirror[] argTypes = type.GetGenericArguments ();
+ if (paramTypes.Length != argTypes.Length)
+ return false;
+
+ for (int i = 0; i < paramTypes.Length; i++) {
+ if (!CheckTypeName (argTypes[i], paramTypes[i]))
+ return false;
+ }
+ } else if (!CheckTypeName (type.CSharpName, name)) {
+ if (!CheckTypeName (type.FullName, name))
+ return false;
+ }
+
+ return true;
+ }
+
+ static bool CheckMethodParams (MethodMirror method, string[] paramTypes)
+ {
+ if (paramTypes == null) {
+ // User supplied no params to match against, match anything we find.
+ return true;
+ }
+
+ var parameters = method.GetParameters ();
+ if (parameters.Length != paramTypes.Length)
+ return false;
+
+ for (int i = 0; i < paramTypes.Length; i++) {
+ if (!CheckTypeName (parameters[i].ParameterType, paramTypes[i]))
+ return false;
+ }
+
+ return true;
+ }
+
+ bool IsGenericMethod (MethodMirror method)
+ {
+ return vm.Version.AtLeast (2, 12) && method.IsGenericMethod;
+ }
+
+ //If paramType == null all overloads are returned
+ IEnumerable<MethodMirror> FindMethodsByName (string function, string[] paramTypes)
+ {
+ if (!started)
+ yield break;
+
+ if (vm.Version.AtLeast (2, 9)) {
+ var bracket = function.IndexOf ('(');
+ int dot;
+ if (bracket != -1) {
+ //Handle stuff like SomeNamespace.SomeType.Method(SomeOtherNamespace.SomeOtherType)
+ dot = function.LastIndexOf ('.', bracket);
+ } else {
+ dot = function.LastIndexOf ('.');
+ }
+ if (dot == -1 || dot + 1 == function.Length)
+ yield break;
+
+ // FIXME: handle types like GenericType<>, GenericType<SomeOtherType>, and GenericType<...>+NestedGenricType<...>
+ string methodName;
+ string typeName = function.Substring (0, dot);
+ if (bracket == -1) {
+ methodName = function.Substring (dot + 1);
+ } else {
+ methodName = function.Substring (dot + 1, bracket - (dot + 1));
+ }
+
+ // FIXME: need a way of querying all types so we can substring match typeName (e.g. user may have typed "Console" instead of "System.Console")
+ foreach (var type in vm.GetTypes (typeName, false)) {
+ ProcessType (type);
+
+ foreach (var method in type.GetMethodsByNameFlags (methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static, false)) {
+ if (!CheckMethodParams (method, paramTypes))
+ continue;
+
+ yield return method;
+ }
+ }
+ }
+
+ yield break;
+ }
+
+ IList<Location> FindLocationsByFile (string file, int line, int column, out bool genericTypeOrMethod, out bool insideLoadedRange)
+ {
+ var locations = new List<Location> ();
+
+ genericTypeOrMethod = false;
+ insideLoadedRange = false;
+
+ if (!started)
+ return locations;
+
+ var filename = Path.GetFileName (file);
+
+ AddFileToSourceMapping (filename);
+
+ lock (source_to_type) {
+ // Try already loaded types in the current source file
+ if (source_to_type.TryGetValue (filename, out var mirrors)) {
+ foreach (var type in mirrors) {
+ var location = FindLocationByType (type, file, line, column, out var genericMethod, out var insideRange);
+ if (insideRange)
+ insideLoadedRange = true;
+
+ if (location != null) {
+ if (genericMethod || type.IsGenericType)
+ genericTypeOrMethod = true;
+
+ locations.Add (location);
+ }
+ }
+ }
+ }
+
+ return locations;
+ }
+
+ private void AddFileToSourceMapping (string filename)
+ {
+ //
+ // Fetch types matching the source file from the debuggee, and add them
+ // to the source file->type mapping tables.
+ // This is needed because we don't receive type load events for all types,
+ // just the ones which match a source file with an existing breakpoint.
+ //
+ if (vm.Version.AtLeast (2, 9)) {
+ IList<TypeMirror> typesInFile;
+ if (vm.Version.AtLeast (2, 12)) {
+ typesInFile = vm.GetTypesForSourceFile (filename, IgnoreFilenameCase);
+ } else {
+ typesInFile = vm.GetTypesForSourceFile (filename, false);
+ //HACK: with older versions of sdb that don't support case-insenitive compares,
+ //explicitly try lowercased drivename on windows, since csc (when not hosted in VS) lowercases
+ //the drivename in the pdb files that get converted to mdbs as-is
+ if (typesInFile.Count == 0 && IsWindows) {
+ string alternateCaseFilename = char.ToLower (filename [0]) + filename.Substring (1);
+ typesInFile = vm.GetTypesForSourceFile (alternateCaseFilename, false);
+ }
+ }
+
+ foreach (TypeMirror t in typesInFile)
+ ProcessType (t);
+ }
+ }
+
+ public override bool CanCancelAsyncEvaluations
+ {
+ get
+ {
+ return Adaptor.IsEvaluating;
+ }
+ }
+
+ protected override void OnCancelAsyncEvaluations ()
+ {
+ Adaptor.CancelAsyncOperations ();
+ }
+
+ protected override void OnNextInstruction ()
+ {
+ Step (StepDepth.Over, StepSize.Min);
+ }
+
+ protected override void OnNextLine ()
+ {
+ Step (StepDepth.Over, StepSize.Line);
+ }
+
+ void Step (StepDepth depth, StepSize size)
+ {
+ try {
+ Adaptor.CancelAsyncOperations (); // This call can block, so it has to run in background thread to avoid keeping the main session lock
+ var req = vm.CreateStepRequest (current_thread);
+ req.Depth = depth;
+ req.Size = size;
+ req.Filter = ShouldFilterStaticCtor () | StepFilter.DebuggerHidden | StepFilter.DebuggerStepThrough;
+ if (Options.ProjectAssembliesOnly)
+ req.Filter |= StepFilter.DebuggerNonUserCode;
+ if (assemblyFilters != null && assemblyFilters.Count > 0)
+ req.AssemblyFilter = assemblyFilters;
+ try {
+ req.Enabled = true;
+ } catch (NotSupportedException e) {
+ if (vm.Version.AtLeast (2, 19)) //catch NotSupportedException thrown by old version of protocol
+ throw e;
+ }
+ currentStepRequest = req;
+ OnResumed ();
+ vm.Resume ();
+ DequeueEventsForFirstThread ();
+ } catch (CommandException ex) {
+ string reason;
+
+ switch (ex.ErrorCode) {
+ case ErrorCode.INVALID_FRAMEID: reason = "invalid frame id"; break;
+ case ErrorCode.NOT_SUSPENDED: reason = "VM not suspended"; break;
+ case ErrorCode.ERR_UNLOADED: reason = "AppDomain has been unloaded"; break;
+ case ErrorCode.NO_SEQ_POINT_AT_IL_OFFSET: reason = "no sequence point at the specified IL offset"; break;
+ default: reason = ex.ErrorCode.ToString (); break;
+ }
+
if (outputOptions.ExceptionMessage)
- OnDebuggerOutput(true, string.Format ("Step request failed: {0}", ex.Message));
- DebuggerLoggingService.LogError ("Step request failed", ex);
- }
+ OnDebuggerOutput (true, string.Format ("Step request failed: {0}.", reason));
+ DebuggerLoggingService.LogError ("Step request failed", ex);
+ } catch (Exception ex) {
+ if (outputOptions.ExceptionMessage)
+ OnDebuggerOutput(true, string.Format ("Step request failed: {0}", ex.Message));
+ DebuggerLoggingService.LogError ("Step request failed", ex);
+ }
}
private StepFilter ShouldFilterStaticCtor()
@@ -1773,1732 +1776,1792 @@ namespace Mono.Debugging.Soft
return (frames.Any(f => f.Method.Name == ".cctor" && f.Method.IsSpecialName && f.Method.IsStatic))
? StepFilter.None : StepFilter.StaticCtor;
}
-
- void EventHandler ()
- {
- int? exit_code = null;
-
- while (true) {
- try {
- EventSet e = vm.GetNextEventSet ();
- var type = e[0].EventType;
- if (type == EventType.VMDeath || type == EventType.VMDisconnect) {
- if (type == EventType.VMDeath && vm.Version.AtLeast (2, 27)) {
- exit_code = ((VMDeathEvent) e[0]).ExitCode;
- }
- break;
- }
- HandleEventSet (e);
- } catch (Exception ex) {
- if (HasExited)
- break;
-
- if (!HandleException (ex) && outputOptions.ExceptionMessage)
- OnDebuggerOutput (true, ex.ToString ());
-
- if (ex is VMDisconnectedException || ex is IOException || ex is SocketException)
- break;
- }
- }
-
- try {
- // This is a workaround for a mono bug
- // Without this call, the process may become zombie in mono < 2.10.2
- if (vm.Process != null)
- vm.Process.WaitForExit (1);
- } catch (SystemException) {
- }
-
- OnTargetEvent (new TargetEventArgs (TargetEventType.TargetExited) {
- ExitCode = exit_code
- });
- }
-
- protected override bool HandleException (Exception ex)
- {
- HideConnectionDialog ();
-
- if (HasExited)
- return true;
-
- if (ex is VMDisconnectedException || ex is IOException) {
- ex = new DisconnectedException (ex);
- HasExited = true;
- } else if (ex is SocketException) {
- ex = new DebugSocketException (ex);
- HasExited = true;
- }
-
- return base.HandleException (ex);
- }
-
- void HandleEventSet (EventSet es)
- {
- var type = es[0].EventType;
-
-#if DEBUG_EVENT_QUEUEING
- if (type != TypeLoadEvent)
- Console.WriteLine ("pp eventset({0}): {1}", es.Events.Length, es[0]);
-#endif
-
- // If we are currently stopped on a thread, and the break events are on a different thread, we must queue
- // that event set and dequeue it next time we resume. This eliminates race conditions when multiple threads
- // hit breakpoints or catchpoints simultaneously.
- //
- bool isBreakEvent = type == EventType.Step || type == EventType.Breakpoint || type == EventType.Exception || type == EventType.UserBreak;
- if (isBreakEvent) {
- if (current_thread != null && es[0].Thread.Id != current_thread.Id) {
- QueueBreakEventSet (es.Events);
- } else {
- HandleBreakEventSet (es.Events, false);
- }
- return;
- }
-
- switch (type) {
- case EventType.AssemblyLoad:
- HandleAssemblyLoadEvents (Array.ConvertAll (es.Events, item => (AssemblyLoadEvent)item));
- break;
- case EventType.AppDomainUnload:
- HandleDomainUnloadEvents (Array.ConvertAll (es.Events, item => (AppDomainUnloadEvent)item));
- break;
- case EventType.VMStart:
- HandleVMStartEvents (Array.ConvertAll (es.Events, item => (VMStartEvent)item));
- break;
- case EventType.TypeLoad:
- HandleTypeLoadEvents (Array.ConvertAll (es.Events, item => (TypeLoadEvent)item));
- break;
- case EventType.ThreadStart:
- HandleThreadStartEvents (Array.ConvertAll (es.Events, item => (ThreadStartEvent)item));
- break;
- case EventType.ThreadDeath:
- HandleThreadDeathEvents (Array.ConvertAll (es.Events, item => (ThreadDeathEvent)item));
- break;
- case EventType.UserLog:
- HandleUserLogEvents (Array.ConvertAll (es.Events, item => (UserLogEvent)item));
- break;
- case EventType.MethodUpdate:
- HandleMethodUpdateEvents (Array.ConvertAll (es.Events, item => (MethodUpdateEvent)item));
- break;
- default:
- DebuggerLoggingService.LogMessage ("Ignoring unknown debugger event type {0}", type);
- break;
- }
-
- try {
- if (!vm.Version.AtLeast (2, 55) || es.SuspendPolicy != SuspendPolicy.None)
- vm.Resume ();
- } catch (VMNotSuspendedException) {
- if (type != EventType.VMStart && vm.Version.AtLeast (2, 2))
- throw;
- }
- }
-
- static bool IsStepIntoRequest (StepEventRequest stepRequest)
- {
- return stepRequest.Depth == StepDepth.Into;
- }
-
- static bool IsStepOutRequest (StepEventRequest stepRequest)
- {
- return stepRequest.Depth == StepDepth.Out;
- }
-
- static bool IsPropertyOrOperatorMethod (MethodMirror method)
- {
- string name = method.Name;
-
- return method.IsSpecialName &&
- (name.StartsWith ("get_", StringComparison.Ordinal) ||
- name.StartsWith ("set_", StringComparison.Ordinal) ||
- name.StartsWith ("op_", StringComparison.Ordinal));
- }
-
- static bool IsCompilerGenerated (MethodMirror method)
- {
- foreach (var attr in method.GetCustomAttributes(false)) {
- if (attr.Constructor.DeclaringType.FullName == "System.Runtime.CompilerServices.CompilerGeneratedAttribute")
- return true;
- }
- return false;
- }
-
- bool IsUserAssembly (AssemblyMirror assembly)
- {
- if (userAssemblyNames == null)
- return true;
-
- var name = assembly.GetName ().FullName;
-
- foreach (var n in userAssemblyNames) {
- if (n == name)
- return true;
- }
-
- return false;
- }
-
- bool IsAutoGeneratedFrameworkEnumerator (TypeMirror type)
- {
- if (IsUserAssembly (type.Assembly))
- return false;
-
- if (!SoftDebuggerAdaptor.IsGeneratedType (type))
- return false;
-
- foreach (var iface in type.GetInterfaces ()) {
- if (iface.Namespace == "System.Collections" && iface.Name == "IEnumerator")
- return true;
- }
-
- return false;
- }
-
- bool StepThrough (MethodMirror method)
- {
- if (Options.ProjectAssembliesOnly && !IsUserAssembly (method.DeclaringType.Assembly))
- return true;
-
- //With Sdb 2.30 this logic was moved to Runtime no need to spend time on checking this
- if (!vm.Version.AtLeast (2, 30)) {
- if (vm.Version.AtLeast (2, 21)) {
- foreach (var attr in method.GetCustomAttributes (false)) {
- var attrName = attr.Constructor.DeclaringType.FullName;
-
- switch (attrName) {
- case "System.Diagnostics.DebuggerHiddenAttribute":
- return true;
- case "System.Diagnostics.DebuggerStepThroughAttribute":
- return true;
- case "System.Diagnostics.DebuggerNonUserCodeAttribute":
- return Options.ProjectAssembliesOnly;
- }
- }
- }
-
- if (Options.ProjectAssembliesOnly) {
- foreach (var attr in method.DeclaringType.GetCustomAttributes (false)) {
- var attrName = attr.Constructor.DeclaringType.FullName;
-
- if (attrName == "System.Diagnostics.DebuggerNonUserCodeAttribute")
- return Options.ProjectAssembliesOnly;
- }
- }
- }
-
- return false;
- }
-
- bool ContinueOnStepInto (MethodMirror method)
- {
- if (vm.Version.AtLeast (2, 21)) {
- foreach (var attr in method.GetCustomAttributes (false)) {
- var attrName = attr.Constructor.DeclaringType.FullName;
-
- if (attrName == "System.Diagnostics.DebuggerStepperBoundaryAttribute")
- return true;
- }
- }
-
- return false;
- }
-
- bool IgnoreBreakpoint (MethodMirror method)
- {
- if (vm.Version.AtLeast (2, 21)) {
- foreach (var attr in method.GetCustomAttributes (false)) {
- var attrName = attr.Constructor.DeclaringType.FullName;
-
- switch (attrName) {
- case "System.Diagnostics.DebuggerHiddenAttribute": return true;
- case "System.Diagnostics.DebuggerStepThroughAttribute": return true;
- case "System.Diagnostics.DebuggerNonUserCodeAttribute": return Options.ProjectAssembliesOnly;
- case "System.Diagnostics.DebuggerStepperBoundaryAttribute": return true;
- }
- }
- }
-
- if (Options.ProjectAssembliesOnly) {
- foreach (var attr in method.DeclaringType.GetCustomAttributes (false)) {
- var attrName = attr.Constructor.DeclaringType.FullName;
-
- if (attrName == "System.Diagnostics.DebuggerNonUserCodeAttribute")
- return Options.ProjectAssembliesOnly;
- }
- }
-
- return false;
- }
-
- /// <summary>
- /// Checks all frames in thread where exception occured and if any frame has user code it returns true.
- /// Also notice that this method already check if Options.ProjectAssembliesOnly==false
- /// </summary>
- bool ExceptionInUserCode (ExceptionEvent ev)
- {
- // this is just optimization to prevent need to fetch Frames
- if (Options.ProjectAssembliesOnly == false)
- return true;
- foreach (var frame in ev.Thread.GetFrames ()) {
- if (!IsExternalCode (frame))
- return true;
- }
- return false;
- }
-
- bool ShouldIgnore (ExceptionEvent ev)
- {
- BreakInfo binfo;
- if (!breakpoints.TryGetValue (ev.Request, out binfo))
- return false;
-
- var cp = binfo.BreakEvent as Catchpoint;
- if (cp == null)
- return false;
-
- var backtrace = GetThreadBacktrace (ev.Thread);
- if (backtrace.FrameCount == 0) {
- return cp.ShouldIgnore (ev.Exception.Type.FullName, null);
- } else {
- var frame = backtrace.GetFrame (0);
- return cp.ShouldIgnore (ev.Exception.Type.FullName, frame.GetLocationSignature ());
- }
- }
-
- void HandleBreakEventSet (Event[] es, bool dequeuing)
- {
- if (dequeuing && HasExited)
- return;
-
- TargetEventType etype = TargetEventType.TargetStopped;
- ObjectMirror exception = null;
- BreakEvent breakEvent = null;
- bool redoCurrentStep = false;
- bool steppedInto = false;
- bool steppedOut = false;
- bool resume = true;
- BreakInfo binfo;
-
- if (es [0].EventType == EventType.Exception) {
- var bad = es.FirstOrDefault (ee => ee.EventType != EventType.Exception);
- if (bad != null)
- throw new Exception ("Catchpoint eventset had unexpected event type " + bad.GetType ());
- var ev = (ExceptionEvent)es [0];
- exception = ev.Exception;
- if (ev.Request == unhandledExceptionRequest) {
- etype = TargetEventType.UnhandledException;
- if (exception.Type.FullName != "System.Threading.ThreadAbortException")
- resume = false;
- } else {
- // Set the exception for this thread so that CatchPoint Print message(tracing) of {$exception} works
- activeExceptionsByThread [es[0].Thread.ThreadId] = exception;
- if (ExceptionInUserCode(ev) && !ShouldIgnore(ev) && !HandleBreakpoint (es [0].Thread, ev.Request)) {
- etype = TargetEventType.ExceptionThrown;
- resume = false;
- }
-
- // Remove exception from the thread so that when the program stops due to stepFinished/programPause/breakPoint...
- // we don't have on out-dated exception(setting and unsetting few lines later is needed because it's used inside HandleBreakpoint)
- activeExceptionsByThread.Remove (es[0].Thread.ThreadId);
-
- // Get the breakEvent so that we can check if we should ignore it later
- if (breakpoints.TryGetValue (ev.Request, out binfo))
- breakEvent = binfo.BreakEvent;
- }
- } else {
- //always need to evaluate all breakpoints, some might be tracepoints or conditional bps with counters
- foreach (Event e in es) {
- if (e.EventType == EventType.Breakpoint) {
- var be = (BreakpointEvent) e;
- var hasBreakInfo = breakpoints.TryGetValue (be.Request, out binfo);
-
- if (!HandleBreakpoint (e.Thread, be.Request)) {
- etype = TargetEventType.TargetHitBreakpoint;
- autoStepInto = false;
- resume = false;
- if (hasBreakInfo)
- breakEvent = binfo.BreakEvent;
- }
-
- if (hasBreakInfo) {
- if (currentStepRequest != null &&
- currentStepRequest.Depth != StepDepth.Out &&
- binfo.Location.ILOffset == currentAddress &&
- e.Thread.Id == currentStepRequest.Thread.Id &&
- currentStackDepth == e.Thread.GetFrames ().Length)
- redoCurrentStep = true;
- }
- } else if (e.EventType == EventType.Step) {
- var stepRequest = e.Request as StepEventRequest;
- steppedInto = IsStepIntoRequest (stepRequest);
- steppedOut = IsStepOutRequest (stepRequest);
- etype = TargetEventType.TargetStopped;
- resume = false;
- } else if (e.EventType == EventType.UserBreak) {
- etype = TargetEventType.TargetStopped;
- autoStepInto = false;
- resume = false;
- } else {
- throw new Exception ("Break eventset had unexpected event type " + e.GetType ());
- }
- }
- }
-
- if (redoCurrentStep) {
- StepDepth depth = currentStepRequest.Depth;
- StepSize size = currentStepRequest.Size;
-
- current_thread = recent_thread = es[0].Thread;
- currentStepRequest.Enabled = false;
- currentStepRequest = null;
-
- Step (depth, size);
- } else if (resume) {
- // all breakpoints were conditional and evaluated as false
- current_thread = null;
- vm.Resume ();
- DequeueEventsForFirstThread ();
- } else {
- if (currentStepRequest != null) {
- currentStepRequest.Enabled = false;
- currentStepRequest = null;
- }
-
- current_thread = recent_thread = es[0].Thread;
-
- if (exception != null)
- activeExceptionsByThread [current_thread.ThreadId] = exception;
-
- var backtrace = GetThreadBacktrace (current_thread);
- bool stepInto = false;
- bool stepOut = false;
-
- if (backtrace.FrameCount > 0) {
- var frame = backtrace.GetFrame (0) as SoftDebuggerStackFrame;
- currentAddress = frame != null ? frame.Address : -1;
- currentStackDepth = backtrace.FrameCount;
-
- if (frame != null && steppedInto) {
- if (ContinueOnStepInto (frame.StackFrame.Method)) {
- current_thread = null;
- vm.Resume ();
- DequeueEventsForFirstThread ();
- return;
- }
-
- if (StepThrough (frame.StackFrame.Method)) {
- // The method has a Debugger[Hidden,StepThrough,NonUserCode]Attribute on it
- // Keep calling StepInto until we land somewhere without one of these attributes
- stepInto = true;
- } else if (frame.StackFrame.ILOffset == 0 && IsPropertyOrOperatorMethod (frame.StackFrame.Method) &&
- (Options.StepOverPropertiesAndOperators || IsCompilerGenerated (frame.StackFrame.Method))) {
- //We want to skip property only when we just stepped into property(ILOffset==0)
- //so if user puts breakpoint inside property we don't want to StepOut for him when he steps after breakpoint is hit
-
- //mcs.exe and Roslyn are also emmiting Sequence point inside auto-properties so breakpoint can be placed
- //we want to always skip auto-properties also when StepOverProperties is disabled hence "|| IsCompilerGenerated"
-
- // We will want to call StepInto once StepOut returns...
- autoStepInto = true;
- stepOut = true;
- } else if (IsAutoGeneratedFrameworkEnumerator (frame.StackFrame.Method.DeclaringType)) {
- // User asked to step in, but we landed in an autogenerated type (probably an iterator)
- autoStepInto = true;
- stepOut = true;
- }
- } else if (etype == TargetEventType.TargetHitBreakpoint && breakEvent != null && !breakEvent.NonUserBreakpoint && IgnoreBreakpoint (frame.StackFrame.Method)) {
- current_thread = null;
- vm.Resume ();
- DequeueEventsForFirstThread ();
- return;
- }
- }
-
- if (stepOut) {
- Step (StepDepth.Out, StepSize.Min);
- } else if (stepInto) {
- Step (StepDepth.Into, StepSize.Min);
- } else if (steppedOut && autoStepInto) {
- autoStepInto = false;
- Step (StepDepth.Into, StepSize.Min);
- } else {
- var args = new TargetEventArgs (etype);
- args.Process = OnGetProcesses () [0];
- args.Thread = GetThread (args.Process, current_thread);
- args.Backtrace = backtrace;
- args.BreakEvent = breakEvent;
-
- OnTargetEvent (args);
- }
- }
- }
-
+
+ void EventHandler ()
+ {
+ int? exit_code = null;
+
+ while (true) {
+ try {
+ EventSet e = vm.GetNextEventSet ();
+ var type = e[0].EventType;
+ if (type == EventType.VMDeath || type == EventType.VMDisconnect) {
+ if (type == EventType.VMDeath && vm.Version.AtLeast (2, 27)) {
+ exit_code = ((VMDeathEvent) e[0]).ExitCode;
+ }
+ break;
+ }
+ HandleEventSet (e);
+ } catch (Exception ex) {
+ if (HasExited)
+ break;
+
+ if (!HandleException (ex) && outputOptions.ExceptionMessage)
+ OnDebuggerOutput (true, ex.ToString ());
+
+ if (ex is VMDisconnectedException || ex is IOException || ex is SocketException)
+ break;
+ }
+ }
+
+ try {
+ // This is a workaround for a mono bug
+ // Without this call, the process may become zombie in mono < 2.10.2
+ if (vm.Process != null)
+ vm.Process.WaitForExit (1);
+ } catch (SystemException) {
+ }
+
+ OnTargetEvent (new TargetEventArgs (TargetEventType.TargetExited) {
+ ExitCode = exit_code
+ });
+ }
+
+ protected override bool HandleException (Exception ex)
+ {
+ HideConnectionDialog ();
+
+ if (HasExited)
+ return true;
+
+ if (ex is VMDisconnectedException || ex is IOException) {
+ ex = new DisconnectedException (ex);
+ HasExited = true;
+ } else if (ex is SocketException) {
+ ex = new DebugSocketException (ex);
+ HasExited = true;
+ }
+
+ return base.HandleException (ex);
+ }
+
+ void HandleEventSet (EventSet es)
+ {
+ var type = es[0].EventType;
+
+#if DEBUG_EVENT_QUEUEING
+ if (type != TypeLoadEvent)
+ Console.WriteLine ("pp eventset({0}): {1}", es.Events.Length, es[0]);
+#endif
+
+ // If we are currently stopped on a thread, and the break events are on a different thread, we must queue
+ // that event set and dequeue it next time we resume. This eliminates race conditions when multiple threads
+ // hit breakpoints or catchpoints simultaneously.
+ //
+ bool isBreakEvent = type == EventType.Step || type == EventType.Breakpoint || type == EventType.Exception || type == EventType.UserBreak;
+ if (isBreakEvent) {
+ if (current_thread != null && es[0].Thread.Id != current_thread.Id) {
+ QueueBreakEventSet (es.Events);
+ } else {
+ HandleBreakEventSet (es.Events, false);
+ }
+ return;
+ }
+
+ switch (type) {
+ case EventType.AssemblyLoad:
+ HandleAssemblyLoadEvents (Array.ConvertAll (es.Events, item => (AssemblyLoadEvent)item));
+ break;
+ case EventType.AppDomainUnload:
+ HandleDomainUnloadEvents (Array.ConvertAll (es.Events, item => (AppDomainUnloadEvent)item));
+ break;
+ case EventType.VMStart:
+ HandleVMStartEvents (Array.ConvertAll (es.Events, item => (VMStartEvent)item));
+ break;
+ case EventType.TypeLoad:
+ HandleTypeLoadEvents (Array.ConvertAll (es.Events, item => (TypeLoadEvent)item));
+ break;
+ case EventType.ThreadStart:
+ HandleThreadStartEvents (Array.ConvertAll (es.Events, item => (ThreadStartEvent)item));
+ break;
+ case EventType.ThreadDeath:
+ HandleThreadDeathEvents (Array.ConvertAll (es.Events, item => (ThreadDeathEvent)item));
+ break;
+ case EventType.UserLog:
+ HandleUserLogEvents (Array.ConvertAll (es.Events, item => (UserLogEvent)item));
+ break;
+ case EventType.MethodUpdate:
+ HandleMethodUpdateEvents (Array.ConvertAll (es.Events, item => (MethodUpdateEvent)item));
+ break;
+ default:
+ DebuggerLoggingService.LogMessage ("Ignoring unknown debugger event type {0}", type);
+ break;
+ }
+
+ try {
+ if (!vm.Version.AtLeast (2, 55) || es.SuspendPolicy != SuspendPolicy.None)
+ vm.Resume ();
+ } catch (VMNotSuspendedException) {
+ if (type != EventType.VMStart && vm.Version.AtLeast (2, 2))
+ throw;
+ }
+ }
+
+ static bool IsStepIntoRequest (StepEventRequest stepRequest)
+ {
+ return stepRequest.Depth == StepDepth.Into;
+ }
+
+ static bool IsStepOutRequest (StepEventRequest stepRequest)
+ {
+ return stepRequest.Depth == StepDepth.Out;
+ }
+
+ static bool IsPropertyOrOperatorMethod (MethodMirror method)
+ {
+ string name = method.Name;
+
+ return method.IsSpecialName &&
+ (name.StartsWith ("get_", StringComparison.Ordinal) ||
+ name.StartsWith ("set_", StringComparison.Ordinal) ||
+ name.StartsWith ("op_", StringComparison.Ordinal));
+ }
+
+ static bool IsCompilerGenerated (MethodMirror method)
+ {
+ foreach (var attr in method.GetCustomAttributes(false)) {
+ if (attr.Constructor.DeclaringType.FullName == "System.Runtime.CompilerServices.CompilerGeneratedAttribute")
+ return true;
+ }
+ return false;
+ }
+
+ bool IsUserAssembly (AssemblyMirror assembly)
+ {
+ if (userAssemblyNames == null)
+ return true;
+
+ var name = assembly.GetName ().FullName;
+
+ foreach (var n in userAssemblyNames) {
+ if (n == name)
+ return true;
+ }
+
+ return false;
+ }
+
+ bool IsAutoGeneratedFrameworkEnumerator (TypeMirror type)
+ {
+ if (IsUserAssembly (type.Assembly))
+ return false;
+
+ if (!SoftDebuggerAdaptor.IsGeneratedType (type))
+ return false;
+
+ foreach (var iface in type.GetInterfaces ()) {
+ if (iface.Namespace == "System.Collections" && iface.Name == "IEnumerator")
+ return true;
+ }
+
+ return false;
+ }
+
+ bool StepThrough (MethodMirror method)
+ {
+ if (Options.ProjectAssembliesOnly && !IsUserAssembly (method.DeclaringType.Assembly))
+ return true;
+
+ //With Sdb 2.30 this logic was moved to Runtime no need to spend time on checking this
+ if (!vm.Version.AtLeast (2, 30)) {
+ if (vm.Version.AtLeast (2, 21)) {
+ foreach (var attr in method.GetCustomAttributes (false)) {
+ var attrName = attr.Constructor.DeclaringType.FullName;
+
+ switch (attrName) {
+ case "System.Diagnostics.DebuggerHiddenAttribute":
+ return true;
+ case "System.Diagnostics.DebuggerStepThroughAttribute":
+ return true;
+ case "System.Diagnostics.DebuggerNonUserCodeAttribute":
+ return Options.ProjectAssembliesOnly;
+ }
+ }
+ }
+
+ if (Options.ProjectAssembliesOnly) {
+ foreach (var attr in method.DeclaringType.GetCustomAttributes (false)) {
+ var attrName = attr.Constructor.DeclaringType.FullName;
+
+ if (attrName == "System.Diagnostics.DebuggerNonUserCodeAttribute")
+ return Options.ProjectAssembliesOnly;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ bool ContinueOnStepInto (MethodMirror method)
+ {
+ if (vm.Version.AtLeast (2, 21)) {
+ foreach (var attr in method.GetCustomAttributes (false)) {
+ var attrName = attr.Constructor.DeclaringType.FullName;
+
+ if (attrName == "System.Diagnostics.DebuggerStepperBoundaryAttribute")
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ bool IgnoreBreakpoint (MethodMirror method)
+ {
+ if (vm.Version.AtLeast (2, 21)) {
+ foreach (var attr in method.GetCustomAttributes (false)) {
+ var attrName = attr.Constructor.DeclaringType.FullName;
+
+ switch (attrName) {
+ case "System.Diagnostics.DebuggerHiddenAttribute": return true;
+ case "System.Diagnostics.DebuggerStepThroughAttribute": return true;
+ case "System.Diagnostics.DebuggerNonUserCodeAttribute": return Options.ProjectAssembliesOnly;
+ case "System.Diagnostics.DebuggerStepperBoundaryAttribute": return true;
+ }
+ }
+ }
+
+ if (Options.ProjectAssembliesOnly) {
+ foreach (var attr in method.DeclaringType.GetCustomAttributes (false)) {
+ var attrName = attr.Constructor.DeclaringType.FullName;
+
+ if (attrName == "System.Diagnostics.DebuggerNonUserCodeAttribute")
+ return Options.ProjectAssembliesOnly;
+ }
+ }
+
+ return false;
+ }
+
+ /// <summary>
+ /// Checks all frames in thread where exception occured and if any frame has user code it returns true.
+ /// Also notice that this method already check if Options.ProjectAssembliesOnly==false
+ /// </summary>
+ bool ExceptionInUserCode (ExceptionEvent ev)
+ {
+ // this is just optimization to prevent need to fetch Frames
+ if (Options.ProjectAssembliesOnly == false)
+ return true;
+ foreach (var frame in ev.Thread.GetFrames ()) {
+ if (!IsExternalCode (frame))
+ return true;
+ }
+ return false;
+ }
+
+ bool ShouldIgnore (ExceptionEvent ev)
+ {
+ BreakInfo binfo;
+ if (!breakpoints.TryGetValue (ev.Request, out binfo))
+ return false;
+
+ var cp = binfo.BreakEvent as Catchpoint;
+ if (cp == null)
+ return false;
+
+ var backtrace = GetThreadBacktrace (ev.Thread);
+ if (backtrace.FrameCount == 0) {
+ return cp.ShouldIgnore (ev.Exception.Type.FullName, null);
+ } else {
+ var frame = backtrace.GetFrame (0);
+ return cp.ShouldIgnore (ev.Exception.Type.FullName, frame.GetLocationSignature ());
+ }
+ }
+
+ void HandleBreakEventSet (Event[] es, bool dequeuing)
+ {
+ if (dequeuing && HasExited)
+ return;
+
+ TargetEventType etype = TargetEventType.TargetStopped;
+ ObjectMirror exception = null;
+ BreakEvent breakEvent = null;
+ bool redoCurrentStep = false;
+ bool steppedInto = false;
+ bool steppedOut = false;
+ bool resume = true;
+ BreakInfo binfo;
+
+ if (es [0].EventType == EventType.Exception) {
+ var bad = es.FirstOrDefault (ee => ee.EventType != EventType.Exception);
+ if (bad != null)
+ throw new Exception ("Catchpoint eventset had unexpected event type " + bad.GetType ());
+ var ev = (ExceptionEvent)es [0];
+ exception = ev.Exception;
+ if (ev.Request == unhandledExceptionRequest) {
+ etype = TargetEventType.UnhandledException;
+ if (exception.Type.FullName != "System.Threading.ThreadAbortException")
+ resume = false;
+ } else {
+ // Set the exception for this thread so that CatchPoint Print message(tracing) of {$exception} works
+ activeExceptionsByThread [es[0].Thread.ThreadId] = exception;
+ if (ExceptionInUserCode(ev) && !ShouldIgnore(ev) && !HandleBreakpoint (es [0].Thread, ev.Request)) {
+ etype = TargetEventType.ExceptionThrown;
+ resume = false;
+ }
+
+ // Remove exception from the thread so that when the program stops due to stepFinished/programPause/breakPoint...
+ // we don't have on out-dated exception(setting and unsetting few lines later is needed because it's used inside HandleBreakpoint)
+ activeExceptionsByThread.Remove (es[0].Thread.ThreadId);
+
+ // Get the breakEvent so that we can check if we should ignore it later
+ if (breakpoints.TryGetValue (ev.Request, out binfo))
+ breakEvent = binfo.BreakEvent;
+ }
+ } else {
+ //always need to evaluate all breakpoints, some might be tracepoints or conditional bps with counters
+ foreach (Event e in es) {
+ if (e.EventType == EventType.Breakpoint) {
+ var be = (BreakpointEvent) e;
+ var hasBreakInfo = breakpoints.TryGetValue (be.Request, out binfo);
+
+ if (!HandleBreakpoint (e.Thread, be.Request)) {
+ etype = TargetEventType.TargetHitBreakpoint;
+ autoStepInto = false;
+ resume = false;
+ if (hasBreakInfo)
+ breakEvent = binfo.BreakEvent;
+ }
+
+ if (hasBreakInfo) {
+ if (currentStepRequest != null &&
+ currentStepRequest.Depth != StepDepth.Out &&
+ binfo.Location.ILOffset == currentAddress &&
+ e.Thread.Id == currentStepRequest.Thread.Id &&
+ currentStackDepth == e.Thread.GetFrames ().Length)
+ redoCurrentStep = true;
+ }
+ } else if (e.EventType == EventType.Step) {
+ var stepRequest = e.Request as StepEventRequest;
+ steppedInto = IsStepIntoRequest (stepRequest);
+ steppedOut = IsStepOutRequest (stepRequest);
+ etype = TargetEventType.TargetStopped;
+ resume = false;
+ } else if (e.EventType == EventType.UserBreak) {
+ etype = TargetEventType.TargetStopped;
+ autoStepInto = false;
+ resume = false;
+ } else {
+ throw new Exception ("Break eventset had unexpected event type " + e.GetType ());
+ }
+ }
+ }
+
+ if (redoCurrentStep) {
+ StepDepth depth = currentStepRequest.Depth;
+ StepSize size = currentStepRequest.Size;
+
+ current_thread = recent_thread = es[0].Thread;
+ currentStepRequest.Enabled = false;
+ currentStepRequest = null;
+
+ Step (depth, size);
+ } else if (resume) {
+ // all breakpoints were conditional and evaluated as false
+ current_thread = null;
+ vm.Resume ();
+ DequeueEventsForFirstThread ();
+ } else {
+ if (currentStepRequest != null) {
+ currentStepRequest.Enabled = false;
+ currentStepRequest = null;
+ }
+
+ current_thread = recent_thread = es[0].Thread;
+
+ if (exception != null)
+ activeExceptionsByThread [current_thread.ThreadId] = exception;
+
+ var backtrace = GetThreadBacktrace (current_thread);
+ bool stepInto = false;
+ bool stepOut = false;
+
+ if (backtrace.FrameCount > 0) {
+ var frame = backtrace.GetFrame (0) as SoftDebuggerStackFrame;
+ currentAddress = frame != null ? frame.Address : -1;
+ currentStackDepth = backtrace.FrameCount;
+
+ if (frame != null && steppedInto) {
+ if (ContinueOnStepInto (frame.StackFrame.Method)) {
+ current_thread = null;
+ vm.Resume ();
+ DequeueEventsForFirstThread ();
+ return;
+ }
+
+ if (StepThrough (frame.StackFrame.Method)) {
+ // The method has a Debugger[Hidden,StepThrough,NonUserCode]Attribute on it
+ // Keep calling StepInto until we land somewhere without one of these attributes
+ stepInto = true;
+ } else if (frame.StackFrame.ILOffset == 0 && IsPropertyOrOperatorMethod (frame.StackFrame.Method) &&
+ (Options.StepOverPropertiesAndOperators || IsCompilerGenerated (frame.StackFrame.Method))) {
+ //We want to skip property only when we just stepped into property(ILOffset==0)
+ //so if user puts breakpoint inside property we don't want to StepOut for him when he steps after breakpoint is hit
+
+ //mcs.exe and Roslyn are also emmiting Sequence point inside auto-properties so breakpoint can be placed
+ //we want to always skip auto-properties also when StepOverProperties is disabled hence "|| IsCompilerGenerated"
+
+ // We will want to call StepInto once StepOut returns...
+ autoStepInto = true;
+ stepOut = true;
+ } else if (IsAutoGeneratedFrameworkEnumerator (frame.StackFrame.Method.DeclaringType)) {
+ // User asked to step in, but we landed in an autogenerated type (probably an iterator)
+ autoStepInto = true;
+ stepOut = true;
+ }
+ } else if (etype == TargetEventType.TargetHitBreakpoint && breakEvent != null && !breakEvent.NonUserBreakpoint && IgnoreBreakpoint (frame.StackFrame.Method)) {
+ current_thread = null;
+ vm.Resume ();
+ DequeueEventsForFirstThread ();
+ return;
+ }
+ }
+
+ if (stepOut) {
+ Step (StepDepth.Out, StepSize.Min);
+ } else if (stepInto) {
+ Step (StepDepth.Into, StepSize.Min);
+ } else if (steppedOut && autoStepInto) {
+ autoStepInto = false;
+ Step (StepDepth.Into, StepSize.Min);
+ } else {
+ var args = new TargetEventArgs (etype);
+ args.Process = OnGetProcesses () [0];
+ args.Thread = GetThread (args.Process, current_thread);
+ args.Backtrace = backtrace;
+ args.BreakEvent = breakEvent;
+
+ OnTargetEvent (args);
+ }
+ }
+ }
+
public void AddUserAssembly(string userAssembly)
{
if (userAssemblyNames != null && !userAssemblyNames.Contains(userAssembly))
userAssemblyNames.Add(userAssembly);
- }
-
- void HandleAssemblyLoadEvents (AssemblyLoadEvent[] events)
- {
- var asm = events [0].Assembly;
- if (events.Length > 1 && events.Any (a => a.Assembly != asm))
- throw new InvalidOperationException ("Simultaneous AssemblyLoadEvent for multiple assemblies");
-
- OnAssemblyLoaded(asm.Location);
-
- RegisterAssembly(asm);
- bool isExternal;
+ }
+
+ void HandleAssemblyLoadEvents (AssemblyLoadEvent[] events)
+ {
+ var asm = events [0].Assembly;
+ if (events.Length > 1 && events.Any (a => a.Assembly != asm))
+ throw new InvalidOperationException ("Simultaneous AssemblyLoadEvent for multiple assemblies");
+
+ var symbolStatus = asm.GetMetadata ().MainModule.HasSymbols ? "Symbol loaded" : "Skipped loading symbols";
+
+ var assembly = new Assembly (
+ asm.GetMetadata ().MainModule.Name,
+ asm.Location,
+ true,
+ asm.GetMetadata ().MainModule.HasSymbols,
+ symbolStatus,
+ "",
+ -1,
+ asm.GetName ().Version.Major.ToString(),
+ // TODO: module time stamp
+ "",
+ asm.GetAssemblyObject ().Address.ToString(),
+ string.Format("[{0}]{1}", asm.VirtualMachine.TargetProcess.Id, asm.VirtualMachine.TargetProcess.ProcessName),
+ asm.Domain.FriendlyName,
+ asm.VirtualMachine.TargetProcess.Id
+ ) ;
+
+ OnAssemblyLoaded (assembly);
+
+ RegisterAssembly(asm);
+ bool isExternal;
isExternal = !UpdateAssemblyFilters (asm) && userAssemblyNames != null;
if (outputOptions.ModuleLoaded) {
- string flagExt = isExternal ? " [External]" : "";
- OnDebuggerOutput (false, string.Format ("Loaded assembly: {0}{1}\n", asm.Location, flagExt));
- }
- }
-
- void RegisterAssembly (AssemblyMirror asm)
- {
- var domain = vm.Version.AtLeast (2, 45) ? asm.Domain : asm.GetAssemblyObject ().Domain;
- if (domainAssembliesToUnload.TryGetValue (domain, out var asmList)) {
- asmList.Add (asm);
- } else {
- domainAssembliesToUnload.Add (domain, new HashSet<AssemblyMirror> (new [] { asm }));
- }
- }
-
- void HandleDomainUnloadEvents (AppDomainUnloadEvent [] events)
- {
- var domain = events [0].Domain;
- if (events.Length > 1 && events.Any (a => a.Domain != domain))
- throw new InvalidOperationException ("Simultaneous DomainUnloadEvents for multiple domains");
- if (domainAssembliesToUnload.TryGetValue (domain, out var asmList)) {
- foreach (var asm in asmList) {
- HandleAssemblyUnloadEvents (asm);
- }
- }
- }
-
- void HandleAssemblyUnloadEvents (AssemblyMirror asm)
- {
- assemblyFilters?.Remove (asm);
-
- // Mark affected breakpoints as pending again
- var affectedBreakpoints = new List<KeyValuePair<EventRequest, BreakInfo>> (breakpoints.Where (x => x.Value.Requests.TryGetValue (x.Key, out var a) && a == asm));
- foreach (var breakpoint in affectedBreakpoints) {
- var affectedRequests = breakpoint.Value.Requests.Where (r => r.Value == asm).ToArray ();
- foreach (var item in affectedRequests) {
- breakpoint.Value.Requests.Remove (item.Key);
- breakpoints.Remove (breakpoint.Key);
- }
- if (!breakpoint.Value.Requests.Any ()) {
- string file = breakpoint.Value.Location.SourceFile;
- int line = breakpoint.Value.Location.LineNumber;
- OnDebuggerOutput (false, string.Format ("Re-pending breakpoint at {0}:{1}\n", file, line));
- breakpoint.Value.SetStatus (BreakEventStatus.NotBound, "Assembly unloaded");
- }
- }
-
- // Remove affected types from the loaded types list
- var affectedTypes = types.SelectMany (l => l.Value).Where (t => t.Assembly == asm).ToArray ();
-
- foreach (var tm in affectedTypes) {
- List<TypeMirror> typesList;
- if (tm.IsNested) {
- if (aliases.TryGetValue (NestedTypeNameToAlias (tm.FullName), out typesList)) {
- typesList.Remove (tm);
- if (typesList.Count == 0)
- aliases.Remove (NestedTypeNameToAlias (tm.FullName));
- }
- }
- if (types.TryGetValue (tm.FullName, out typesList)) {
- typesList.Remove (tm);
- if (typesList.Count == 0)
- types.Remove (tm.FullName);
- }
- }
-
- lock (source_to_type) {
- foreach (var pair in source_to_type)
- pair.Value.RemoveAll (m => m.Assembly == asm);
- }
-
- if (outputOptions.ModuleUnoaded)
- OnDebuggerOutput (false, string.Format ("Unloaded assembly: {0}\n", GetAssemblyLocation (asm) ?? "<unknown>"));
- }
-
- static string GetAssemblyLocation (AssemblyMirror asm)
- {
- if (asm == null)
- return null;
- try {
- return asm.Location;
- } catch (CommandException ex) {
- if (ex.ErrorCode != ErrorCode.ERR_UNLOADED)
- throw ex;
- return null;
- }
- }
-
- void HandleVMStartEvents (VMStartEvent[] events)
- {
- var thread = events [0].Thread;
- if (events.Length > 1)
- throw new InvalidOperationException ("Simultaneous VMStartEvents");
-
- OnStarted (new ThreadInfo (0, GetId (thread), GetThreadName (thread), null));
- //HACK: 2.6.1 VM doesn't emit type load event, so work around it
- var t = vm.RootDomain.Corlib.GetType ("System.Exception", false, false);
- if (t != null) {
- ResolveBreakpoints (t);
- }
- }
-
- void HandleTypeLoadEvents (TypeLoadEvent[] events)
- {
- var type = events [0].Type;
- if (events.Length > 1 && events.Any (a => a.Type != type))
- throw new InvalidOperationException ("Simultaneous TypeLoadEvents for multiple types");
-
- List<TypeMirror> typesList;
- if (!(types.TryGetValue (type.FullName, out typesList) && typesList.Contains (type)))
- ResolveBreakpoints (type);
- }
-
- void HandleThreadStartEvents (ThreadStartEvent[] events)
- {
- current_threads = null;
- var thread = events [0].Thread;
- if (events.Length > 1 && events.Any (a => a.Thread != thread))
- throw new InvalidOperationException ("Simultaneous ThreadStartEvents for multiple threads");
-
- var name = GetThreadName (thread);
- var id = GetId (thread);
- OnDebuggerOutput (false, string.Format ("Thread started: {0} #{1}\n", name, id));
- OnTargetEvent (new TargetEventArgs (TargetEventType.ThreadStarted) {
- Thread = new ThreadInfo (0, id, name, null),
- });
- }
-
- void HandleThreadDeathEvents (ThreadDeathEvent[] events)
- {
- current_threads = null;
- ThreadMirror thread;
- try {
- thread = events [0].Thread;
- } catch (ObjectCollectedException) {
- // A 2.1 debugger-agent can send a ThreadDeath event for a ThreadMirror
- // already collected, in that case just allow the session to continue
- if (!vm.Version.AtLeast (2, 2))
- return;
-
- // Otherwise just report the error
- throw;
- }
- if (events.Length > 1 && events.Any (a => a.Thread != thread))
- throw new InvalidOperationException ("Simultaneous ThreadDeathEvents for multiple threads");
-
- var name = GetThreadName (thread);
- var id = GetId (thread);
- if (outputOptions.ThreadExited)
- OnDebuggerOutput (false, string.Format ("Thread finished: {0} #{1}\n", name, id));
- OnTargetEvent (new TargetEventArgs (TargetEventType.ThreadStopped) {
- Thread = new ThreadInfo (0, id, name, null),
- });
- }
-
- void HandleUserLogEvents (UserLogEvent[] events)
- {
- foreach (var ul in events)
- OnTargetDebug (ul.Level, ul.Category, ul.Message);
- }
-
+ string flagExt = isExternal ? " [External]" : "";
+ OnDebuggerOutput (false, string.Format ("Loaded assembly: {0}{1}\n", asm.Location, flagExt));
+ }
+ }
+
+ void RegisterAssembly (AssemblyMirror asm)
+ {
+ var domain = vm.Version.AtLeast (2, 45) ? asm.Domain : asm.GetAssemblyObject ().Domain;
+ if (domainAssembliesToUnload.TryGetValue (domain, out var asmList)) {
+ asmList.Add (asm);
+ } else {
+ domainAssembliesToUnload.Add (domain, new HashSet<AssemblyMirror> (new [] { asm }));
+ }
+ }
+
+ void HandleDomainUnloadEvents (AppDomainUnloadEvent [] events)
+ {
+ var domain = events [0].Domain;
+ if (events.Length > 1 && events.Any (a => a.Domain != domain))
+ throw new InvalidOperationException ("Simultaneous DomainUnloadEvents for multiple domains");
+ if (domainAssembliesToUnload.TryGetValue (domain, out var asmList)) {
+ foreach (var asm in asmList) {
+ HandleAssemblyUnloadEvents (asm);
+ }
+ }
+ }
+
+ void HandleAssemblyUnloadEvents (AssemblyMirror asm)
+ {
+ assemblyFilters?.Remove (asm);
+
+ // Mark affected breakpoints as pending again
+ var affectedBreakpoints = new List<KeyValuePair<EventRequest, BreakInfo>> (breakpoints.Where (x => x.Value.Requests.TryGetValue (x.Key, out var a) && a == asm));
+ foreach (var breakpoint in affectedBreakpoints) {
+ var affectedRequests = breakpoint.Value.Requests.Where (r => r.Value == asm).ToArray ();
+ foreach (var item in affectedRequests) {
+ breakpoint.Value.Requests.Remove (item.Key);
+ breakpoints.Remove (breakpoint.Key);
+ }
+ if (!breakpoint.Value.Requests.Any ()) {
+ string file = breakpoint.Value.Location.SourceFile;
+ int line = breakpoint.Value.Location.LineNumber;
+ OnDebuggerOutput (false, string.Format ("Re-pending breakpoint at {0}:{1}\n", file, line));
+ breakpoint.Value.SetStatus (BreakEventStatus.NotBound, "Assembly unloaded");
+ }
+ }
+
+ // Remove affected types from the loaded types list
+ var affectedTypes = types.SelectMany (l => l.Value).Where (t => t.Assembly == asm).ToArray ();
+
+ foreach (var tm in affectedTypes) {
+ List<TypeMirror> typesList;
+ if (tm.IsNested) {
+ if (aliases.TryGetValue (NestedTypeNameToAlias (tm.FullName), out typesList)) {
+ typesList.Remove (tm);
+ if (typesList.Count == 0)
+ aliases.Remove (NestedTypeNameToAlias (tm.FullName));
+ }
+ }
+ if (types.TryGetValue (tm.FullName, out typesList)) {
+ typesList.Remove (tm);
+ if (typesList.Count == 0)
+ types.Remove (tm.FullName);
+ }
+ }
+
+ lock (source_to_type) {
+ foreach (var pair in source_to_type)
+ pair.Value.RemoveAll (m => m.Assembly == asm);
+ }
+
+ if (outputOptions.ModuleUnoaded)
+ OnDebuggerOutput (false, string.Format ("Unloaded assembly: {0}\n", GetAssemblyLocation (asm) ?? "<unknown>"));
+ }
+
+ static string GetAssemblyLocation (AssemblyMirror asm)
+ {
+ if (asm == null)
+ return null;
+ try {
+ return asm.Location;
+ } catch (CommandException ex) {
+ if (ex.ErrorCode != ErrorCode.ERR_UNLOADED)
+ throw ex;
+ return null;
+ }
+ }
+
+ void HandleVMStartEvents (VMStartEvent[] events)
+ {
+ var thread = events [0].Thread;
+ if (events.Length > 1)
+ throw new InvalidOperationException ("Simultaneous VMStartEvents");
+
+ OnStarted (new ThreadInfo (0, GetId (thread), GetThreadName (thread), null));
+ //HACK: 2.6.1 VM doesn't emit type load event, so work around it
+ var t = vm.RootDomain.Corlib.GetType ("System.Exception", false, false);
+ if (t != null) {
+ ResolveBreakpoints (t);
+ }
+ }
+
+ void HandleTypeLoadEvents (TypeLoadEvent[] events)
+ {
+ var type = events [0].Type;
+ if (events.Length > 1 && events.Any (a => a.Type != type))
+ throw new InvalidOperationException ("Simultaneous TypeLoadEvents for multiple types");
+
+ List<TypeMirror> typesList;
+ if (!(types.TryGetValue (type.FullName, out typesList) && typesList.Contains (type)))
+ ResolveBreakpoints (type);
+ }
+
+ void HandleThreadStartEvents (ThreadStartEvent[] events)
+ {
+ current_threads = null;
+ var thread = events [0].Thread;
+ if (events.Length > 1 && events.Any (a => a.Thread != thread))
+ throw new InvalidOperationException ("Simultaneous ThreadStartEvents for multiple threads");
+
+ var name = GetThreadName (thread);
+ var id = GetId (thread);
+ OnDebuggerOutput (false, string.Format ("Thread started: {0} #{1}\n", name, id));
+ OnTargetEvent (new TargetEventArgs (TargetEventType.ThreadStarted) {
+ Thread = new ThreadInfo (0, id, name, null),
+ });
+ }
+
+ void HandleThreadDeathEvents (ThreadDeathEvent[] events)
+ {
+ current_threads = null;
+ ThreadMirror thread;
+ try {
+ thread = events [0].Thread;
+ } catch (ObjectCollectedException) {
+ // A 2.1 debugger-agent can send a ThreadDeath event for a ThreadMirror
+ // already collected, in that case just allow the session to continue
+ if (!vm.Version.AtLeast (2, 2))
+ return;
+
+ // Otherwise just report the error
+ throw;
+ }
+ if (events.Length > 1 && events.Any (a => a.Thread != thread))
+ throw new InvalidOperationException ("Simultaneous ThreadDeathEvents for multiple threads");
+
+ var name = GetThreadName (thread);
+ var id = GetId (thread);
+ if (outputOptions.ThreadExited)
+ OnDebuggerOutput (false, string.Format ("Thread finished: {0} #{1}\n", name, id));
+ OnTargetEvent (new TargetEventArgs (TargetEventType.ThreadStopped) {
+ Thread = new ThreadInfo (0, id, name, null),
+ });
+ }
+
+ void HandleUserLogEvents (UserLogEvent[] events)
+ {
+ foreach (var ul in events)
+ OnTargetDebug (ul.Level, ul.Category, ul.Message);
+ }
+
void HandleMethodUpdateEvents(MethodUpdateEvent[] methods)
{
- foreach (var method in methods)
+ foreach (var method in methods) //add new methods to type
{
- foreach (var bp in breakpoints) {
- if (bp.Value.Location.Method.GetId() == method.GetMethod().GetId())
- {
- bool dummy = false;
- var l = FindLocationByMethod (bp.Value.Location.Method, bp.Value.Location.SourceFile, bp.Value.Location.LineNumber, bp.Value.Location.ColumnNumber, ref dummy);
- bp.Value.Location = l;
- UpdateBreakpoint ((Breakpoint)bp.Value.BreakEvent, bp.Value);
+ method.GetMethod ().ClearCachedLocalsDebugInfo ();
+ method.GetMethod ().DeclaringType.AddMethodIfNotExist (method.GetMethod ());
+ }
+ foreach (var breakpoint in breakpoints) {
+ Breakpoint bp = ((Breakpoint)breakpoint.Value.BreakEvent);
+ if (bp.UpdatedByEnC) {
+ bool dummy = false;
+ var l = FindLocationByMethod (breakpoint.Value.Location.Method, bp.FileName, bp.Line, bp.Column, ref dummy);
+ if (l != null) {
+ breakpoint.Value.Location = l;
+ UpdateBreakpoint (bp, breakpoint.Value);
+ bp.UpdatedByEnC = false;
+ }
+ }
+ }
+ foreach (var bp in pending_bes) {
+ if (bp.Status != BreakEventStatus.Bound) {
+ foreach (var location in FindLocationsByFile (bp.Breakpoint.FileName, bp.Breakpoint.Line, bp.Breakpoint.Column, out _, out bool insideLoadedRange)) {
+ OnDebuggerOutput (false, string.Format ("Resolved pending breakpoint at '{0}:{1},{2}' to {3} [0x{4:x5}].\n",
+ bp.Breakpoint.FileName, bp.Breakpoint.Line, bp.Breakpoint.Column,
+ GetPrettyMethodName (location.Method), location.ILOffset));
+
+ bp.Location = location;
+ InsertBreakpoint (bp.Breakpoint, bp);
+ bp.SetStatus (BreakEventStatus.Bound, null);
+ }
+ }
+ }
+ }
+
+ public ObjectMirror GetExceptionObject (ThreadMirror thread)
+ {
+ ObjectMirror obj;
+
+ return activeExceptionsByThread.TryGetValue (thread.ThreadId, out obj) ? obj : null;
+ }
+
+ void QueueBreakEventSet (Event[] eventSet)
+ {
+#if DEBUG_EVENT_QUEUEING
+ Console.WriteLine ("qq eventset({0}): {1}", eventSet.Length, eventSet[0]);
+#endif
+ var events = new List<Event> (eventSet);
+ lock (queuedEventSets) {
+ queuedEventSets.AddLast (events);
+ }
+ }
+
+ void RemoveQueuedBreakEvents (Dictionary<EventRequest, AssemblyMirror> requests)
+ {
+ int resume = 0;
+
+ lock (queuedEventSets) {
+ var node = queuedEventSets.First;
+
+ while (node != null) {
+ List<Event> q = node.Value;
+
+ for (int i = 0; i < q.Count; i++) {
+ foreach (var request in requests.Keys) {
+ if (q[i].Request == request) {
+ q.RemoveAt (i--);
+ break;
+ }
+ }
+ }
+
+ if (q.Count == 0) {
+ var d = node;
+ node = node.Next;
+ queuedEventSets.Remove (d);
+ resume++;
+ } else {
+ node = node.Next;
+ }
+ }
+ }
+
+ for (int i = 0; i < resume; i++)
+ vm.Resume ();
+ }
+
+ void DequeueEventsForFirstThread ()
+ {
+ List<List<Event>> dequeuing;
+ lock (queuedEventSets) {
+ if (queuedEventSets.Count < 1)
+ return;
+
+ dequeuing = new List<List<Event>> ();
+ var node = queuedEventSets.First;
+
+ //making this the current thread means that all events from other threads will get queued
+ current_thread = node.Value[0].Thread;
+ while (node != null) {
+ if (node.Value[0].Thread.Id == current_thread.Id) {
+ var d = node;
+ node = node.Next;
+ dequeuing.Add (d.Value);
+ queuedEventSets.Remove (d);
+ } else {
+ node = node.Next;
+ }
+ }
+ }
+
+#if DEBUG_EVENT_QUEUEING
+ foreach (var e in dequeuing)
+ Console.WriteLine ("dq eventset({0}): {1}", e.Count, e[0]);
+#endif
+
+ //firing this off in a thread prevents possible infinite recursion
+ ThreadPool.QueueUserWorkItem (delegate {
+ if (!HasExited) {
+ foreach (var es in dequeuing) {
+ try {
+ HandleBreakEventSet (es.ToArray (), true);
+ } catch (Exception ex) {
+ if (!HandleException (ex) && outputOptions.ExceptionMessage)
+ OnDebuggerOutput (true, ex.ToString ());
+
+ if (ex is VMDisconnectedException || ex is IOException || ex is SocketException) {
+ OnTargetEvent (new TargetEventArgs (TargetEventType.TargetExited));
+ break;
+ }
+ }
+ }
+ }
+ });
+ }
+
+ bool HandleBreakpoint (ThreadMirror thread, EventRequest er)
+ {
+ BreakInfo binfo;
+ if (!breakpoints.TryGetValue (er, out binfo))
+ return false;
+
+ var bp = binfo.BreakEvent;
+ if (bp == null)
+ return false;
+
+ binfo.IncrementHitCount ();
+ if (!binfo.HitCountReached)
+ return true;
+
+ if (!string.IsNullOrEmpty (bp.ConditionExpression)) {
+ string res = EvaluateExpression (thread, bp.ConditionExpression, bp);
+ if (bp.BreakIfConditionChanges) {
+ if (res == binfo.LastConditionValue)
+ return true;
+ binfo.LastConditionValue = res;
+ } else {
+ if (res == null || res.ToLowerInvariant () != "true")
+ return true;
+ }
+ }
+ if ((bp.HitAction & HitAction.CustomAction) != HitAction.None) {
+ if (HandleProcessStartHook (thread, bp))
+ return true;
+ // If custom action returns true, execution must continue
+ return binfo.RunCustomBreakpointAction (bp.CustomActionId);
+ }
+
+ if ((bp.HitAction & HitAction.PrintTrace) != HitAction.None) {
+ OnTargetDebug (0, "", "Breakpoint reached: " + binfo.FileName + ":" + binfo.Location.LineNumber + Environment.NewLine);
+ }
+
+ if ((bp.HitAction & HitAction.PrintExpression) != HitAction.None) {
+ string exp = EvaluateTrace (thread, bp.TraceExpression);
+ binfo.UpdateLastTraceValue (exp);
+ }
+
+ // Continue execution if we don't have break action.
+ return (bp.HitAction & HitAction.Break) == HitAction.None;
+ }
+
+ string EvaluateTrace (ThreadMirror thread, string exp)
+ {
+ var sb = new StringBuilder ();
+ int last = 0;
+ int i = exp.IndexOf ('{');
+ while (i != -1) {
+ if (i < exp.Length - 1 && exp [i+1] == '{') {
+ sb.Append (exp, last, i - last + 1);
+ last = i + 2;
+ i = exp.IndexOf ('{', i + 2);
+ continue;
+ }
+ int j = exp.IndexOf ('}', i + 1);
+ if (j == -1)
+ break;
+ string se = exp.Substring (i + 1, j - i - 1);
+ se = EvaluateExpression (thread, se, null);
+ sb.Append (exp, last, i - last);
+ sb.Append (se);
+ last = j + 1;
+ i = exp.IndexOf ('{', last);
+ }
+ sb.Append (exp, last, exp.Length - last);
+ return sb.ToString ();
+ }
+
+ static SourceLocation GetSourceLocation (StackFrame frame)
+ {
+ return new SourceLocation (frame.Method.Name, frame.FileName, frame.LineNumber, frame.ColumnNumber, frame.EndLineNumber, frame.EndColumnNumber);
+ }
+
+ static string FormatSourceLocation (BreakEvent breakEvent)
+ {
+ var bp = breakEvent as Breakpoint;
+ if (bp == null || string.IsNullOrEmpty (bp.FileName))
+ return null;
+
+ var location = Path.GetFileName (bp.FileName);
+ if (bp.OriginalLine > 0) {
+ location += ":" + bp.OriginalLine;
+ if (bp.OriginalColumn > 0)
+ location += "," + bp.OriginalColumn;
+ }
+
+ return location;
+ }
+
+ static bool IsBoolean (ValueReference vr)
+ {
+ if (vr.Type is Type && ((Type) vr.Type) == typeof (bool))
+ return true;
+
+ if (vr.Type is TypeMirror && ((TypeMirror) vr.Type).FullName == "System.Boolean")
+ return true;
+
+ return false;
+ }
+
+ string EvaluateExpression (ThreadMirror thread, string expression, BreakEvent bp)
+ {
+ try {
+ var frames = thread.GetFrames ();
+ if (frames.Length == 0)
+ return string.Empty;
+
+ EvaluationOptions ops = Options.EvaluationOptions.Clone ();
+ ops.AllowTargetInvoke = true;
+ ops.EllipsizedLength = 1000;
+
+ var ctx = new SoftEvaluationContext (this, frames[0], ops);
+
+ if (bp != null) {
+ // validate conditional breakpoint expressions so that we can provide error reporting to the user
+ var vr = ctx.Evaluator.ValidateExpression (ctx, expression);
+ if (!vr.IsValid) {
+ string message = string.Format ("Invalid expression in conditional breakpoint. {0}", vr.Message);
+ string location = FormatSourceLocation (bp);
+
+ if (!string.IsNullOrEmpty (location))
+ message = location + ": " + message;
+
+ OnDebuggerOutput (true, message);
+ return string.Empty;
+ }
+
+ // resolve types...
+ if (ctx.SourceCodeAvailable)
+ expression = ctx.Evaluator.Resolve (this, GetSourceLocation (frames[0]), expression);
+ }
+
+ ValueReference val = ctx.Evaluator.Evaluate (ctx, expression);
+ if (bp != null && !bp.BreakIfConditionChanges && !IsBoolean (val)) {
+ string message = string.Format ("Expression in conditional breakpoint did not evaluate to a boolean value: {0}", bp.ConditionExpression);
+ string location = FormatSourceLocation (bp);
+
+ if (!string.IsNullOrEmpty (location))
+ message = location + ": " + message;
+
+ OnDebuggerOutput (true, message);
+ return string.Empty;
+ }
+
+ return val.CreateObjectValue (false).Value;
+ } catch (EvaluatorException ex) {
+ string message;
+
+ if (bp != null) {
+ message = string.Format ("Failed to evaluate expression in conditional breakpoint. {0}", ex.Message);
+ string location = FormatSourceLocation (bp);
+
+ if (!string.IsNullOrEmpty (location))
+ message = location + ": " + message;
+ } else {
+ message = ex.ToString ();
+ }
+
+ OnDebuggerOutput (true, message);
+ return string.Empty;
+ } catch (Exception ex) {
+ OnDebuggerOutput (true, ex.ToString ());
+ return string.Empty;
+ }
+ }
+
+ static string NestedTypeNameToAlias (string typeName)
+ {
+ int index = typeName.IndexOfAny (new [] { '[', ',' });
+
+ if (index == -1)
+ return typeName.Replace ('+', '.');
+
+ var prefix = typeName.Substring (0, index).Replace ('+', '.');
+ var suffix = typeName.Substring (index);
+
+ return prefix + suffix;
+ }
+
+ void ProcessType (TypeMirror t)
+ {
+ string typeName = t.FullName;
+
+ List<TypeMirror> typesList;
+ if (types.TryGetValue (typeName, out typesList) && typesList.Contains (t))
+ return;
+
+ RegisterAssembly (t.Assembly);
+
+ if (t.IsNested) {
+ var alias = NestedTypeNameToAlias (typeName);
+ List<TypeMirror> aliasesList;
+ if (aliases.TryGetValue (alias, out aliasesList)) {
+ aliasesList.Add (t);
+ } else {
+ aliases [alias] = new List<TypeMirror> (new [] { t });
+ }
+ }
+ types [typeName] = typesList ?? new List<TypeMirror> ();
+ types [typeName].Add (t);
+
+ //get the source file paths
+ //full paths, from GetSourceFiles (true), are only supported by sdb protocol 2.2 and later
+ string[] sourceFiles;
+ if (vm.Version.AtLeast (2, 2)) {
+ sourceFiles = t.GetSourceFiles ().Select ((fullPath) => Path.GetFileName (fullPath)).ToArray ();
+ } else {
+ sourceFiles = t.GetSourceFiles ();
+
+ //HACK: if mdb paths are windows paths but the sdb agent is on unix, it won't map paths to filenames correctly
+ if (IsWindows) {
+ for (int i = 0; i < sourceFiles.Length; i++) {
+ string s = sourceFiles[i];
+ if (s != null && !s.StartsWith ("/", StringComparison.Ordinal))
+ sourceFiles[i] = Path.GetFileName (s);
+ }
+ }
+ }
+
+ for (int n = 0; n < sourceFiles.Length; n++)
+ sourceFiles[n] = NormalizePath (sourceFiles[n]);
+
+ foreach (string s in sourceFiles) {
+ lock (source_to_type) {
+ if (source_to_type.TryGetValue (s, out typesList)) {
+ typesList.Add (t);
+ } else {
+ typesList = new List<TypeMirror> ();
+ typesList.Add (t);
+ source_to_type[s] = typesList;
}
}
}
- }
-
- public ObjectMirror GetExceptionObject (ThreadMirror thread)
- {
- ObjectMirror obj;
-
- return activeExceptionsByThread.TryGetValue (thread.ThreadId, out obj) ? obj : null;
- }
-
- void QueueBreakEventSet (Event[] eventSet)
- {
-#if DEBUG_EVENT_QUEUEING
- Console.WriteLine ("qq eventset({0}): {1}", eventSet.Length, eventSet[0]);
-#endif
- var events = new List<Event> (eventSet);
- lock (queuedEventSets) {
- queuedEventSets.AddLast (events);
- }
- }
-
- void RemoveQueuedBreakEvents (Dictionary<EventRequest, AssemblyMirror> requests)
- {
- int resume = 0;
-
- lock (queuedEventSets) {
- var node = queuedEventSets.First;
-
- while (node != null) {
- List<Event> q = node.Value;
-
- for (int i = 0; i < q.Count; i++) {
- foreach (var request in requests.Keys) {
- if (q[i].Request == request) {
- q.RemoveAt (i--);
- break;
- }
- }
- }
-
- if (q.Count == 0) {
- var d = node;
- node = node.Next;
- queuedEventSets.Remove (d);
- resume++;
- } else {
- node = node.Next;
- }
- }
- }
-
- for (int i = 0; i < resume; i++)
- vm.Resume ();
- }
-
- void DequeueEventsForFirstThread ()
- {
- List<List<Event>> dequeuing;
- lock (queuedEventSets) {
- if (queuedEventSets.Count < 1)
- return;
-
- dequeuing = new List<List<Event>> ();
- var node = queuedEventSets.First;
-
- //making this the current thread means that all events from other threads will get queued
- current_thread = node.Value[0].Thread;
- while (node != null) {
- if (node.Value[0].Thread.Id == current_thread.Id) {
- var d = node;
- node = node.Next;
- dequeuing.Add (d.Value);
- queuedEventSets.Remove (d);
- } else {
- node = node.Next;
- }
- }
- }
-
-#if DEBUG_EVENT_QUEUEING
- foreach (var e in dequeuing)
- Console.WriteLine ("dq eventset({0}): {1}", e.Count, e[0]);
-#endif
-
- //firing this off in a thread prevents possible infinite recursion
- ThreadPool.QueueUserWorkItem (delegate {
- if (!HasExited) {
- foreach (var es in dequeuing) {
- try {
- HandleBreakEventSet (es.ToArray (), true);
- } catch (Exception ex) {
- if (!HandleException (ex) && outputOptions.ExceptionMessage)
- OnDebuggerOutput (true, ex.ToString ());
-
- if (ex is VMDisconnectedException || ex is IOException || ex is SocketException) {
- OnTargetEvent (new TargetEventArgs (TargetEventType.TargetExited));
- break;
- }
- }
- }
- }
- });
- }
-
- bool HandleBreakpoint (ThreadMirror thread, EventRequest er)
- {
- BreakInfo binfo;
- if (!breakpoints.TryGetValue (er, out binfo))
- return false;
-
- var bp = binfo.BreakEvent;
- if (bp == null)
- return false;
-
- binfo.IncrementHitCount ();
- if (!binfo.HitCountReached)
- return true;
-
- if (!string.IsNullOrEmpty (bp.ConditionExpression)) {
- string res = EvaluateExpression (thread, bp.ConditionExpression, bp);
- if (bp.BreakIfConditionChanges) {
- if (res == binfo.LastConditionValue)
- return true;
- binfo.LastConditionValue = res;
- } else {
- if (res == null || res.ToLowerInvariant () != "true")
- return true;
- }
- }
- if ((bp.HitAction & HitAction.CustomAction) != HitAction.None) {
- if (HandleProcessStartHook (thread, bp))
- return true;
- // If custom action returns true, execution must continue
- return binfo.RunCustomBreakpointAction (bp.CustomActionId);
- }
-
- if ((bp.HitAction & HitAction.PrintTrace) != HitAction.None) {
- OnTargetDebug (0, "", "Breakpoint reached: " + binfo.FileName + ":" + binfo.Location.LineNumber + Environment.NewLine);
- }
-
- if ((bp.HitAction & HitAction.PrintExpression) != HitAction.None) {
- string exp = EvaluateTrace (thread, bp.TraceExpression);
- binfo.UpdateLastTraceValue (exp);
- }
-
- // Continue execution if we don't have break action.
- return (bp.HitAction & HitAction.Break) == HitAction.None;
- }
-
- string EvaluateTrace (ThreadMirror thread, string exp)
- {
- var sb = new StringBuilder ();
- int last = 0;
- int i = exp.IndexOf ('{');
- while (i != -1) {
- if (i < exp.Length - 1 && exp [i+1] == '{') {
- sb.Append (exp, last, i - last + 1);
- last = i + 2;
- i = exp.IndexOf ('{', i + 2);
- continue;
- }
- int j = exp.IndexOf ('}', i + 1);
- if (j == -1)
- break;
- string se = exp.Substring (i + 1, j - i - 1);
- se = EvaluateExpression (thread, se, null);
- sb.Append (exp, last, i - last);
- sb.Append (se);
- last = j + 1;
- i = exp.IndexOf ('{', last);
- }
- sb.Append (exp, last, exp.Length - last);
- return sb.ToString ();
- }
-
- static SourceLocation GetSourceLocation (StackFrame frame)
- {
- return new SourceLocation (frame.Method.Name, frame.FileName, frame.LineNumber, frame.ColumnNumber, frame.EndLineNumber, frame.EndColumnNumber);
- }
-
- static string FormatSourceLocation (BreakEvent breakEvent)
- {
- var bp = breakEvent as Breakpoint;
- if (bp == null || string.IsNullOrEmpty (bp.FileName))
- return null;
-
- var location = Path.GetFileName (bp.FileName);
- if (bp.OriginalLine > 0) {
- location += ":" + bp.OriginalLine;
- if (bp.OriginalColumn > 0)
- location += "," + bp.OriginalColumn;
- }
-
- return location;
- }
-
- static bool IsBoolean (ValueReference vr)
- {
- if (vr.Type is Type && ((Type) vr.Type) == typeof (bool))
- return true;
-
- if (vr.Type is TypeMirror && ((TypeMirror) vr.Type).FullName == "System.Boolean")
- return true;
-
- return false;
- }
-
- string EvaluateExpression (ThreadMirror thread, string expression, BreakEvent bp)
- {
- try {
- var frames = thread.GetFrames ();
- if (frames.Length == 0)
- return string.Empty;
-
- EvaluationOptions ops = Options.EvaluationOptions.Clone ();
- ops.AllowTargetInvoke = true;
- ops.EllipsizedLength = 1000;
-
- var ctx = new SoftEvaluationContext (this, frames[0], ops);
-
- if (bp != null) {
- // validate conditional breakpoint expressions so that we can provide error reporting to the user
- var vr = ctx.Evaluator.ValidateExpression (ctx, expression);
- if (!vr.IsValid) {
- string message = string.Format ("Invalid expression in conditional breakpoint. {0}", vr.Message);
- string location = FormatSourceLocation (bp);
-
- if (!string.IsNullOrEmpty (location))
- message = location + ": " + message;
-
- OnDebuggerOutput (true, message);
- return string.Empty;
- }
-
- // resolve types...
- if (ctx.SourceCodeAvailable)
- expression = ctx.Evaluator.Resolve (this, GetSourceLocation (frames[0]), expression);
- }
-
- ValueReference val = ctx.Evaluator.Evaluate (ctx, expression);
- if (bp != null && !bp.BreakIfConditionChanges && !IsBoolean (val)) {
- string message = string.Format ("Expression in conditional breakpoint did not evaluate to a boolean value: {0}", bp.ConditionExpression);
- string location = FormatSourceLocation (bp);
-
- if (!string.IsNullOrEmpty (location))
- message = location + ": " + message;
-
- OnDebuggerOutput (true, message);
- return string.Empty;
- }
-
- return val.CreateObjectValue (false).Value;
- } catch (EvaluatorException ex) {
- string message;
-
- if (bp != null) {
- message = string.Format ("Failed to evaluate expression in conditional breakpoint. {0}", ex.Message);
- string location = FormatSourceLocation (bp);
-
- if (!string.IsNullOrEmpty (location))
- message = location + ": " + message;
- } else {
- message = ex.ToString ();
- }
-
- OnDebuggerOutput (true, message);
- return string.Empty;
- } catch (Exception ex) {
- OnDebuggerOutput (true, ex.ToString ());
- return string.Empty;
- }
- }
-
- static string NestedTypeNameToAlias (string typeName)
- {
- int index = typeName.IndexOfAny (new [] { '[', ',' });
-
- if (index == -1)
- return typeName.Replace ('+', '.');
-
- var prefix = typeName.Substring (0, index).Replace ('+', '.');
- var suffix = typeName.Substring (index);
-
- return prefix + suffix;
- }
-
- void ProcessType (TypeMirror t)
- {
- string typeName = t.FullName;
-
- List<TypeMirror> typesList;
- if (types.TryGetValue (typeName, out typesList) && typesList.Contains (t))
- return;
-
- RegisterAssembly (t.Assembly);
-
- if (t.IsNested) {
- var alias = NestedTypeNameToAlias (typeName);
- List<TypeMirror> aliasesList;
- if (aliases.TryGetValue (alias, out aliasesList)) {
- aliasesList.Add (t);
- } else {
- aliases [alias] = new List<TypeMirror> (new [] { t });
- }
- }
- types [typeName] = typesList ?? new List<TypeMirror> ();
- types [typeName].Add (t);
-
- //get the source file paths
- //full paths, from GetSourceFiles (true), are only supported by sdb protocol 2.2 and later
- string[] sourceFiles;
- if (vm.Version.AtLeast (2, 2)) {
- sourceFiles = t.GetSourceFiles ().Select ((fullPath) => Path.GetFileName (fullPath)).ToArray ();
- } else {
- sourceFiles = t.GetSourceFiles ();
-
- //HACK: if mdb paths are windows paths but the sdb agent is on unix, it won't map paths to filenames correctly
- if (IsWindows) {
- for (int i = 0; i < sourceFiles.Length; i++) {
- string s = sourceFiles[i];
- if (s != null && !s.StartsWith ("/", StringComparison.Ordinal))
- sourceFiles[i] = Path.GetFileName (s);
- }
- }
- }
-
- for (int n = 0; n < sourceFiles.Length; n++)
- sourceFiles[n] = NormalizePath (sourceFiles[n]);
-
- foreach (string s in sourceFiles) {
- lock (source_to_type) {
- if (source_to_type.TryGetValue (s, out typesList)) {
- typesList.Add (t);
- } else {
- typesList = new List<TypeMirror> ();
- typesList.Add (t);
- source_to_type[s] = typesList;
- }
- }
- }
-
- type_to_source [t] = sourceFiles;
- }
-
- static string[] GetParamTypes (MethodMirror method)
- {
- var paramTypes = new List<string> ();
-
- foreach (var param in method.GetParameters ())
- paramTypes.Add (param.ParameterType.CSharpName);
-
- return paramTypes.ToArray ();
- }
-
- string GetPrettyMethodName (MethodMirror method)
- {
- var name = new StringBuilder ();
-
- name.Append (Adaptor.GetDisplayTypeName (method.ReturnType.FullName));
- name.Append (" ");
- name.Append (Adaptor.GetDisplayTypeName (method.DeclaringType.FullName));
- name.Append (".");
- name.Append (method.Name);
-
- if (method.VirtualMachine.Version.AtLeast (2, 12)) {
- if (method.IsGenericMethodDefinition || method.IsGenericMethod) {
- name.Append ("<");
- if (method.VirtualMachine.Version.AtLeast (2, 15)) {
- var argTypes = method.GetGenericArguments ();
- for (int i = 0; i < argTypes.Length; i++) {
- if (i != 0)
- name.Append (", ");
- name.Append (Adaptor.GetDisplayTypeName (argTypes[i].FullName));
- }
- }
- name.Append (">");
- }
- }
-
- name.Append (" (");
- var @params = method.GetParameters ();
- for (int i = 0; i < @params.Length; i++) {
- if (i != 0)
- name.Append (", ");
- if (@params[i].Attributes.HasFlag (ParameterAttributes.Out)) {
- if (@params[i].Attributes.HasFlag (ParameterAttributes.In))
- name.Append ("ref ");
- else
- name.Append ("out ");
- }
- name.Append (Adaptor.GetDisplayTypeName (@params[i].ParameterType.FullName));
- name.Append (" ");
- name.Append (@params[i].Name);
- }
- name.Append (")");
-
- return name.ToString ();
- }
-
- void ResolveBreakpoints (TypeMirror type)
- {
- Location loc;
-
+
+ type_to_source [t] = sourceFiles;
+ }
+
+ static string[] GetParamTypes (MethodMirror method)
+ {
+ var paramTypes = new List<string> ();
+
+ foreach (var param in method.GetParameters ())
+ paramTypes.Add (param.ParameterType.CSharpName);
+
+ return paramTypes.ToArray ();
+ }
+
+ string GetPrettyMethodName (MethodMirror method)
+ {
+ var name = new StringBuilder ();
+
+ name.Append (Adaptor.GetDisplayTypeName (method.ReturnType.FullName));
+ name.Append (" ");
+ name.Append (Adaptor.GetDisplayTypeName (method.DeclaringType.FullName));
+ name.Append (".");
+ name.Append (method.Name);
+
+ if (method.VirtualMachine.Version.AtLeast (2, 12)) {
+ if (method.IsGenericMethodDefinition || method.IsGenericMethod) {
+ name.Append ("<");
+ if (method.VirtualMachine.Version.AtLeast (2, 15)) {
+ var argTypes = method.GetGenericArguments ();
+ for (int i = 0; i < argTypes.Length; i++) {
+ if (i != 0)
+ name.Append (", ");
+ name.Append (Adaptor.GetDisplayTypeName (argTypes[i].FullName));
+ }
+ }
+ name.Append (">");
+ }
+ }
+
+ name.Append (" (");
+ var @params = method.GetParameters ();
+ for (int i = 0; i < @params.Length; i++) {
+ if (i != 0)
+ name.Append (", ");
+ if (@params[i].Attributes.HasFlag (ParameterAttributes.Out)) {
+ if (@params[i].Attributes.HasFlag (ParameterAttributes.In))
+ name.Append ("ref ");
+ else
+ name.Append ("out ");
+ }
+ name.Append (Adaptor.GetDisplayTypeName (@params[i].ParameterType.FullName));
+ name.Append (" ");
+ name.Append (@params[i].Name);
+ }
+ name.Append (")");
+
+ return name.ToString ();
+ }
+
+ void ResolveBreakpoints (TypeMirror type)
+ {
+ Location loc;
+
ProcessType (type);
- // First, resolve FunctionBreakpoints
- BreakInfo [] tempPendingBes;
- lock (pending_bes) {
- tempPendingBes = pending_bes.Where (b => b.BreakEvent is FunctionBreakpoint).ToArray ();
- }
- foreach (var bi in tempPendingBes) {
- if (CheckTypeName (type, bi.TypeName)) {
- var bp = (FunctionBreakpoint)bi.BreakEvent;
- foreach (var method in FindMethodsByName (bp.FunctionName, bp.ParamTypes)) {
- ResolveFunctionBreakpoint (bi, bp, method);
- }
- }
- }
-
- // Now resolve normal Breakpoints
- foreach (string s in type_to_source [type]) {
- lock (pending_bes) {
- tempPendingBes = pending_bes.Where (b => (b.BreakEvent is Breakpoint) && !(b.BreakEvent is FunctionBreakpoint)).ToArray ();
- }
- foreach (var bi in tempPendingBes) {
- var bp = (Breakpoint) bi.BreakEvent;
- if (PathComparer.Compare (Path.GetFileName (bp.FileName), s) == 0) {
- bool insideLoadedRange;
- bool genericMethod;
-
- if (bi.BreakEvent is InstructionBreakpoint) {
- loc = FindLocationByILOffset ((InstructionBreakpoint)bi.BreakEvent, bp.FileName, out genericMethod, out insideLoadedRange);
- } else {
- loc = FindLocationByType (type, bp.FileName, bp.Line, bp.Column, out genericMethod, out insideLoadedRange);
- }
- if (loc != null) {
- OnDebuggerOutput (false, string.Format ("Resolved pending breakpoint at '{0}:{1},{2}' to {3} [0x{4:x5}].\n",
- s, bp.Line, bp.Column, GetPrettyMethodName (loc.Method), loc.ILOffset));
- ResolvePendingBreakpoint (bi, loc);
- } else {
- if (insideLoadedRange) {
- bi.SetStatus (BreakEventStatus.Invalid, null);
- }
- }
- }
- }
- }
-
- // Thirdly, resolve pending catchpoints
- lock (pending_bes) {
- tempPendingBes = pending_bes.Where (b => b.BreakEvent is Catchpoint).ToArray ();
- }
- foreach (var bi in tempPendingBes) {
- var cp = (Catchpoint) bi.BreakEvent;
- if (cp.ExceptionName == type.FullName) {
- ResolvePendingCatchpoint (bi, type);
- }
- }
- }
-
- bool ResolveFunctionBreakpoint (BreakInfo bi, FunctionBreakpoint bp, MethodMirror method)
- {
- var loc = method.Locations.FirstOrDefault ();
- string paramList = "(" + string.Join (", ", bp.ParamTypes ?? GetParamTypes (method)) + ")";
- if (loc != null) {
- bi.Location = loc;
- InsertBreakpoint (bp, bi);
- OnDebuggerOutput (false, string.Format ("Resolved pending breakpoint for '{0}{1}' to {2}:{3} [0x{4:x5}].\n",
- bp.FunctionName, paramList, loc.SourceFile, loc.LineNumber, loc.ILOffset));
- } else {
- InsertBreakpoint (bp, bi, method, 0);
- OnDebuggerOutput (false, string.Format ("Resolved pending breakpoint for '{0}{1}' to [0x0](no debug symbols).\n",
- bp.FunctionName, paramList));
- }
- bi.SetStatus (BreakEventStatus.Bound, null);
- // Note: if the type or method is generic, there may be more instances so don't assume we are done resolving the breakpoint
- return bp.ParamTypes != null && !method.DeclaringType.IsGenericType && !IsGenericMethod (method);
- }
-
- internal static string NormalizePath (string path)
- {
- if (!IsWindows && (path.StartsWith ("\\", StringComparison.Ordinal) || (path.Length > 3 && path [1] == ':' && path [2] == '\\')))
- return path.Replace ('\\', '/');
-
- return path;
- }
-
- [DllImport ("libc")]
- static extern IntPtr realpath (string path, IntPtr buffer);
-
- static string ResolveFullPath (string path)
- {
- if (IsWindows)
- return Path.GetFullPath (path);
-
- const int PATHMAX = 4096 + 1;
- IntPtr buffer = IntPtr.Zero;
-
- try {
- buffer = Marshal.AllocHGlobal (PATHMAX);
- var result = realpath (path, buffer);
- var realPath = result == IntPtr.Zero ? "" : Marshal.PtrToStringAuto (buffer);
-
- if (string.IsNullOrEmpty(realPath) && !File.Exists(path)) {
- // if the file does not exist then `realpath` will return empty string
- // default to what we would do if calling this on windows
- realPath = Path.GetFullPath (path);
- }
-
- return realPath;
- } finally {
- if (buffer != IntPtr.Zero)
- Marshal.FreeHGlobal (buffer);
- }
- }
-
- static string ResolveSymbolicLink (string path)
- {
- if (path.Length == 0)
- return path;
-
- if (IsWindows)
- return ResolveWindowsSymbolicLink (path);
-
- return ResolveUnixSymbolicLink (path);
- }
-
+ // First, resolve FunctionBreakpoints
+ BreakInfo [] tempPendingBes;
+ lock (pending_bes) {
+ tempPendingBes = pending_bes.Where (b => b.BreakEvent is FunctionBreakpoint).ToArray ();
+ }
+ foreach (var bi in tempPendingBes) {
+ if (CheckTypeName (type, bi.TypeName)) {
+ var bp = (FunctionBreakpoint)bi.BreakEvent;
+ foreach (var method in FindMethodsByName (bp.FunctionName, bp.ParamTypes)) {
+ ResolveFunctionBreakpoint (bi, bp, method);
+ }
+ }
+ }
+
+ // Now resolve normal Breakpoints
+ foreach (string s in type_to_source [type]) {
+ lock (pending_bes) {
+ tempPendingBes = pending_bes.Where (b => (b.BreakEvent is Breakpoint) && !(b.BreakEvent is FunctionBreakpoint)).ToArray ();
+ }
+ foreach (var bi in tempPendingBes) {
+ var bp = (Breakpoint) bi.BreakEvent;
+ if (PathComparer.Compare (Path.GetFileName (bp.FileName), s) == 0) {
+ bool insideLoadedRange;
+ bool genericMethod;
+
+ if (bi.BreakEvent is InstructionBreakpoint) {
+ loc = FindLocationByILOffset ((InstructionBreakpoint)bi.BreakEvent, bp.FileName, out genericMethod, out insideLoadedRange);
+ } else {
+ loc = FindLocationByType (type, bp.FileName, bp.Line, bp.Column, out genericMethod, out insideLoadedRange);
+ }
+ if (loc != null) {
+ OnDebuggerOutput (false, string.Format ("Resolved pending breakpoint at '{0}:{1},{2}' to {3} [0x{4:x5}].\n",
+ s, bp.Line, bp.Column, GetPrettyMethodName (loc.Method), loc.ILOffset));
+ ResolvePendingBreakpoint (bi, loc);
+ } else {
+ if (insideLoadedRange) {
+ bi.SetStatus (BreakEventStatus.Invalid, null);
+ }
+ }
+ }
+ }
+ }
+
+ // Thirdly, resolve pending catchpoints
+ lock (pending_bes) {
+ tempPendingBes = pending_bes.Where (b => b.BreakEvent is Catchpoint).ToArray ();
+ }
+ foreach (var bi in tempPendingBes) {
+ var cp = (Catchpoint) bi.BreakEvent;
+ if (cp.ExceptionName == type.FullName) {
+ ResolvePendingCatchpoint (bi, type);
+ }
+ }
+ }
+
+ bool ResolveFunctionBreakpoint (BreakInfo bi, FunctionBreakpoint bp, MethodMirror method)
+ {
+ var loc = method.Locations.FirstOrDefault ();
+ string paramList = "(" + string.Join (", ", bp.ParamTypes ?? GetParamTypes (method)) + ")";
+ if (loc != null) {
+ bi.Location = loc;
+ InsertBreakpoint (bp, bi);
+ OnDebuggerOutput (false, string.Format ("Resolved pending breakpoint for '{0}{1}' to {2}:{3} [0x{4:x5}].\n",
+ bp.FunctionName, paramList, loc.SourceFile, loc.LineNumber, loc.ILOffset));
+ } else {
+ InsertBreakpoint (bp, bi, method, 0);
+ OnDebuggerOutput (false, string.Format ("Resolved pending breakpoint for '{0}{1}' to [0x0](no debug symbols).\n",
+ bp.FunctionName, paramList));
+ }
+ bi.SetStatus (BreakEventStatus.Bound, null);
+ // Note: if the type or method is generic, there may be more instances so don't assume we are done resolving the breakpoint
+ return bp.ParamTypes != null && !method.DeclaringType.IsGenericType && !IsGenericMethod (method);
+ }
+
+ internal static string NormalizePath (string path)
+ {
+ if (!IsWindows && (path.StartsWith ("\\", StringComparison.Ordinal) || (path.Length > 3 && path [1] == ':' && path [2] == '\\')))
+ return path.Replace ('\\', '/');
+
+ return path;
+ }
+
+ [DllImport ("libc")]
+ static extern IntPtr realpath (string path, IntPtr buffer);
+
+ static string ResolveFullPath (string path)
+ {
+ if (IsWindows)
+ return Path.GetFullPath (path);
+
+ const int PATHMAX = 4096 + 1;
+ IntPtr buffer = IntPtr.Zero;
+
+ try {
+ buffer = Marshal.AllocHGlobal (PATHMAX);
+ var result = realpath (path, buffer);
+ var realPath = result == IntPtr.Zero ? "" : Marshal.PtrToStringAuto (buffer);
+
+ if (string.IsNullOrEmpty(realPath) && !File.Exists(path)) {
+ // if the file does not exist then `realpath` will return empty string
+ // default to what we would do if calling this on windows
+ realPath = Path.GetFullPath (path);
+ }
+
+ return realPath;
+ } finally {
+ if (buffer != IntPtr.Zero)
+ Marshal.FreeHGlobal (buffer);
+ }
+ }
+
+ static string ResolveSymbolicLink (string path)
+ {
+ if (path.Length == 0)
+ return path;
+
+ if (IsWindows)
+ return ResolveWindowsSymbolicLink (path);
+
+ return ResolveUnixSymbolicLink (path);
+ }
+
static string ResolveWindowsSymbolicLink (string path)
{
return Path.GetFullPath (path);
- }
-
+ }
+
static string ResolveUnixSymbolicLink (string path)
{
- try {
- var alreadyVisted = new HashSet<string> ();
-
- while (true) {
- if (alreadyVisted.Contains (path))
- return string.Empty;
-
- alreadyVisted.Add (path);
-
- var linkInfo = new Mono.Unix.UnixSymbolicLinkInfo (path);
- if (linkInfo.IsSymbolicLink && linkInfo.HasContents) {
- string contentsPath = linkInfo.ContentsPath;
-
- if (!Path.IsPathRooted (contentsPath))
- path = Path.Combine (Path.GetDirectoryName (path), contentsPath);
- else
- path = contentsPath;
-
- path = ResolveFullPath (path);
- continue;
- }
-
- path = Path.Combine (ResolveSymbolicLink (Path.GetDirectoryName (path)), Path.GetFileName (path));
-
- return ResolveFullPath (path);
- }
- } catch {
- return path;
- }
- }
-
- static bool PathsAreEqual (string p1, string p2)
- {
- if (string.IsNullOrWhiteSpace (p1) || string.IsNullOrWhiteSpace (p2))
- return false;
-
- if (PathComparer.Compare (p1, p2) == 0)
- return true;
-
- var rp1 = ResolveSymbolicLink (p1);
- var rp2 = ResolveSymbolicLink (p2);
-
- return PathComparer.Compare (rp1, rp2) == 0;
- }
-
- Location FindLocationByMethod (MethodMirror method, string file, int line, int column, ref bool insideTypeRange)
- {
- int rangeFirstLine = int.MaxValue;
- int rangeLastLine = -1;
- Location target = null;
-
- //Console.WriteLine ("FindLocationByMethod: method = {0}, file = {1}, line = {2}, column = {3}", method.Name, Path.GetFileName (file), line, column);
-
- foreach (var location in method.Locations) {
- var srcFile = location.SourceFile;
-
- //Console.WriteLine ("\tChecking location {0}:{1},{2}...", Path.GetFileName(srcFile), location.LineNumber, location.ColumnNumber);
-
- //Check if file names match
- if (srcFile != null && PathComparer.Compare (Path.GetFileName (NormalizePath (srcFile)), Path.GetFileName (file)) == 0) {
- //Check if full path match(we don't care about md5 if full path match):
- //1. For backward compatibility
- //2. If full path matches user himself probably modified code and is aware of modifications
- //OR if md5 match, useful for alternative location files with breakpoints
- if (!PathsAreEqual (NormalizePath (srcFile), file) && !SourceLocation.CheckFileHash (file, location.SourceFileHash))
- continue;
-
- if (location.LineNumber < rangeFirstLine)
- rangeFirstLine = location.LineNumber;
-
- if (location.LineNumber > rangeLastLine)
- rangeLastLine = location.LineNumber;
-
- if (line >= rangeFirstLine && line <= rangeLastLine)
- insideTypeRange = true;
-
- if (line == location.LineNumber && (column <= 1 || column == location.ColumnNumber)) {
- if (target != null) {
- if (location.ILOffset < target.ILOffset)
- target = location;
- } else {
- target = location;
- }
- }
- } else {
- rangeFirstLine = int.MaxValue;
- rangeLastLine = -1;
- }
- }
-
- return target;
- }
-
- Location FindLocationByType (TypeMirror type, string file, int line, int column, out bool genericMethod, out bool insideTypeRange)
- {
- var methodInsideTypeRange = false;
- Location target = null;
-
- insideTypeRange = false;
- genericMethod = false;
-
- foreach (var method in type.GetMethods ()) {
- Location location = null;
-
- if ((location = FindLocationByMethod (method, file, line, column, ref methodInsideTypeRange)) != null) {
- insideTypeRange |= methodInsideTypeRange;//If any method returns true return true
-
- if (target != null) {
- // Use the location with the lowest ILOffset
- if (location.ILOffset < target.ILOffset) {
- genericMethod = IsGenericMethod (method);
- target = location;
- }
- } else {
- genericMethod = IsGenericMethod (method);
- target = location;
- }
- }
- }
-
- return target;
- }
-
- void ResolvePendingBreakpoint (BreakInfo bi, Location l)
- {
- bi.Location = l;
- InsertBreakpoint ((Breakpoint) bi.BreakEvent, bi);
- bi.SetStatus (BreakEventStatus.Bound, null);
- }
-
- void ResolvePendingCatchpoint (BreakInfo bi, TypeMirror type)
- {
- InsertCatchpoint ((Catchpoint) bi.BreakEvent, bi, type);
- bi.SetStatus (BreakEventStatus.Bound, null);
- }
-
- bool UpdateAssemblyFilters (AssemblyMirror asm)
- {
- var name = asm.GetName ().FullName;
- bool found = false;
- if (userAssemblyNames != null) {
- //HACK: not sure how else to handle xsp-compiled pages
- if (name.StartsWith ("App_", StringComparison.Ordinal)) {
- found = true;
- } else {
- foreach (var n in userAssemblyNames) {
- if (n == name) {
- found = true;
- break;
- }
- }
- }
- }
-
- if (found) {
- assemblyFilters.Add (asm);
- return true;
- }
-
- return false;
- }
-
- internal void WriteDebuggerOutput (bool isError, string msg)
- {
- OnDebuggerOutput (isError, msg);
- }
-
- protected override void OnSetActiveThread (long processId, long threadId)
- {
- }
-
- protected override void OnStepInstruction ()
- {
- Step (StepDepth.Into, StepSize.Min);
- }
-
- protected override void OnStepLine ()
- {
- Step (StepDepth.Into, StepSize.Line);
- }
-
- protected override void OnStop ()
- {
- vm.Suspend ();
-
- //emit a stop event at the current position of the most recent thread
- //we use "getprocesses" instead of "ongetprocesses" because it attaches the process to the session
- //using private Mono.Debugging API, so our thread/backtrace calls will cache stuff that will get used later
- var process = GetProcesses () [0];
- EnsureRecentThreadIsValid (process);
- current_thread = recent_thread;
- OnTargetEvent (new TargetEventArgs (TargetEventType.TargetStopped) {
- Process = process,
- Thread = GetThread (process, recent_thread),
- Backtrace = GetThreadBacktrace (recent_thread)});
- }
-
- void EnsureRecentThreadIsValid (ProcessInfo process)
- {
- var infos = process.GetThreads ();
-
- if (ThreadIsAlive (recent_thread) && HasUserFrame (GetId (recent_thread), infos))
- return;
-
- var threads = vm.GetThreads ();
- foreach (var thread in threads) {
- if (ThreadIsAlive (thread) && HasUserFrame (GetId (thread), infos)) {
- recent_thread = thread;
- return;
- }
- }
- recent_thread = threads[0];
- }
-
- long GetId (ThreadMirror thread)
- {
- long id;
- if (!localThreadIds.TryGetValue (thread.ThreadId, out id)) {
- id = localThreadIds.Count + 1;
- localThreadIds [thread.ThreadId] = id;
- }
- return id;
- }
-
- static bool ThreadIsAlive (ThreadMirror thread)
- {
- if (thread == null)
- return false;
- ThreadState state;
- try {
- state = thread.ThreadState;
- } catch (ObjectCollectedException) {
- return false;//Thread was already collected by garbage collector, hence it's not alive
- }
- return state != ThreadState.Stopped && state != ThreadState.Aborted;
- }
-
- //we use the Mono.Debugging classes because they are cached
- static bool HasUserFrame (long tid, ThreadInfo[] threads)
- {
- foreach (var thread in threads) {
- if (thread.Id != tid)
- continue;
-
- var bt = thread.Backtrace;
- for (int i = 0; i < bt.FrameCount; i++) {
- var frame = bt.GetFrame (i);
- if (frame != null && !frame.IsExternalCode)
- return true;
- }
-
- return false;
- }
-
- return false;
- }
-
- public bool IsExternalCode (StackFrame frame)
- {
- return frame.Method == null || string.IsNullOrEmpty (frame.FileName)
- || (assemblyFilters != null && !assemblyFilters.Contains (frame.Method.DeclaringType.Assembly));
- }
-
- public bool IsExternalCode (TypeMirror type)
- {
- return assemblyFilters != null && !assemblyFilters.Contains (type.Assembly);
- }
-
- protected override AssemblyLine[] OnDisassembleFile (string file)
- {
- List<TypeMirror> mirrors;
-
- if (!source_to_type.TryGetValue (file, out mirrors))
- return new AssemblyLine [0];
-
- var lines = new List<AssemblyLine> ();
- foreach (var type in mirrors) {
- foreach (var method in type.GetMethods ()) {
- string srcFile = method.SourceFile != null ? NormalizePath (method.SourceFile) : null;
-
- if (srcFile == null || !PathsAreEqual (srcFile, file))
- continue;
-
- var body = method.GetMethodBody ();
- int lastLine = -1;
- int firstPos = lines.Count;
- string addrSpace = method.FullName;
-
- foreach (var ins in body.Instructions) {
- var loc = method.LocationAtILOffset (ins.Offset);
-
- if (loc != null && lastLine == -1) {
- lastLine = loc.LineNumber;
- for (int n = firstPos; n < lines.Count; n++) {
- AssemblyLine old = lines [n];
- lines [n] = new AssemblyLine (old.Address, old.AddressSpace, old.Code, loc.LineNumber);
- }
- }
-
- lines.Add (new AssemblyLine (ins.Offset, addrSpace, Disassemble (ins), loc != null ? loc.LineNumber : lastLine));
- }
- }
- }
-
- lines.Sort (delegate (AssemblyLine a1, AssemblyLine a2) {
- int res = a1.SourceLine.CompareTo (a2.SourceLine);
-
- return res != 0 ? res : a1.Address.CompareTo (a2.Address);
- });
-
- return lines.ToArray ();
- }
-
- public AssemblyLine[] Disassemble (StackFrame frame)
- {
- var body = frame.Method.GetMethodBody ();
- var instructions = body.Instructions;
- var lines = new List<AssemblyLine> ();
-
- foreach (var instruction in instructions) {
- var location = frame.Method.LocationAtILOffset (instruction.Offset);
- int lineNumber = location != null ? location.LineNumber : -1;
- var code = Disassemble (instruction);
-
- lines.Add (new AssemblyLine (instruction.Offset, frame.Method.FullName, code, lineNumber));
- }
-
- return lines.ToArray ();
- }
-
- public AssemblyLine[] Disassemble (StackFrame frame, int firstLine, int count)
- {
- var body = frame.Method.GetMethodBody ();
- var instructions = body.Instructions;
- ILInstruction current = null;
-
- foreach (var instruction in instructions) {
- if (instruction.Offset >= frame.ILOffset) {
- current = instruction;
- break;
- }
- }
-
- if (current == null)
- return new AssemblyLine [0];
-
- var lines = new List<AssemblyLine> ();
-
- while (firstLine < 0 && count > 0) {
- if (current.Previous == null) {
- lines.Add (AssemblyLine.OutOfRange);
- firstLine = 0;
- break;
- }
-
- current = current.Previous;
- firstLine++;
- }
-
- while (current != null && firstLine > 0) {
- current = current.Next;
- firstLine--;
- }
-
- while (count > 0) {
- if (current != null) {
- var location = frame.Method.LocationAtILOffset (current.Offset);
- int lineNumber = location != null ? location.LineNumber : -1;
- var code = Disassemble (current);
-
- lines.Add (new AssemblyLine (current.Offset, frame.Method.FullName, code, lineNumber));
- current = current.Next;
- } else {
- lines.Add (AssemblyLine.OutOfRange);
- }
-
- count--;
- }
-
- return lines.ToArray ();
- }
-
- public void ApplyChanges (ModuleMirror module, byte[] metadataDelta, byte[] ilDelta, byte[] pdbDelta = null)
- {
- var rootDomain = VirtualMachine.RootDomain;
- var metadataArray = rootDomain.CreateByteArray (metadataDelta);
- var ilArray = rootDomain.CreateByteArray (ilDelta);
- Value pdbArray;
- if (pdbDelta == null)
- pdbArray = VirtualMachine.CreateValue (null);
- else
- pdbArray = rootDomain.CreateByteArray (pdbDelta);
-
- module.ApplyChanges (metadataArray, ilArray, pdbArray);
- }
- static string EscapeString (string text)
- {
- var escaped = new StringBuilder ();
-
- escaped.Append ('"');
- for (int i = 0; i < text.Length; i++) {
- char c = text[i];
- string txt;
- switch (c) {
- case '"': txt = "\\\""; break;
- case '\0': txt = @"\0"; break;
- case '\\': txt = @"\\"; break;
- case '\a': txt = @"\a"; break;
- case '\b': txt = @"\b"; break;
- case '\f': txt = @"\f"; break;
- case '\v': txt = @"\v"; break;
- case '\n': txt = @"\n"; break;
- case '\r': txt = @"\r"; break;
- case '\t': txt = @"\t"; break;
- default:
- if (char.GetUnicodeCategory (c) == UnicodeCategory.OtherNotAssigned) {
- escaped.AppendFormat ("\\u{0:X4}", c);
- } else {
- escaped.Append (c);
- }
- continue;
- }
- escaped.Append (txt);
- }
- escaped.Append ('"');
-
- return escaped.ToString ();
- }
-
- static string Disassemble (ILInstruction ins)
- {
- string oper;
- if (ins.Operand is MethodMirror)
- oper = ((MethodMirror)ins.Operand).FullName;
- else if (ins.Operand is FieldInfoMirror)
- oper = ((FieldInfoMirror)ins.Operand).FullName;
- else if (ins.Operand is TypeMirror)
- oper = ((TypeMirror)ins.Operand).FullName;
- else if (ins.Operand is ILInstruction)
- oper = ((ILInstruction)ins.Operand).Offset.ToString ("x8");
- else if (ins.Operand is string)
- oper = EscapeString ((string) ins.Operand);
- else if (ins.Operand == null)
- oper = string.Empty;
- else
- oper = ins.Operand.ToString ();
-
- return ins.OpCode + " " + oper;
- }
-
- readonly static bool IsWindows;
- readonly static bool IsMac;
- readonly static StringComparer PathComparer;
-
- static bool IgnoreFilenameCase {
- get { return IsMac || IsWindows; }
- }
-
- static SoftDebuggerSession ()
- {
- IsWindows = Path.DirectorySeparatorChar == '\\';
- IsMac = !IsWindows && IsRunningOnMac ();
- PathComparer = IgnoreFilenameCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal;
- ThreadMirror.NativeTransitions = true;
- }
-
- //From Managed.Windows.Forms/XplatUI
- static bool IsRunningOnMac ()
- {
- IntPtr buf = IntPtr.Zero;
- try {
- buf = Marshal.AllocHGlobal (8192);
- // This is a hacktastic way of getting sysname from uname ()
- if (uname (buf) == 0) {
- string os = Marshal.PtrToStringAnsi (buf);
- if (os == "Darwin")
- return true;
- }
- } catch {
- return false;
- } finally {
- if (buf != IntPtr.Zero)
- Marshal.FreeHGlobal (buf);
- }
- return false;
- }
-
- [System.Runtime.InteropServices.DllImport ("libc")]
- static extern int uname (IntPtr buf);
- }
-
- class LocationComparer : IComparer<Location>
- {
- public int Compare (Location loc0, Location loc1)
- {
- if (loc0.LineNumber < loc1.LineNumber)
- return -1;
- if (loc0.LineNumber > loc1.LineNumber)
- return 1;
-
- if (loc0.ColumnNumber < loc1.ColumnNumber)
- return -1;
- if (loc0.ColumnNumber > loc1.ColumnNumber)
- return 1;
-
- return loc0.ILOffset - loc1.ILOffset;
- }
- }
-
- class BreakInfo: BreakEventInfo
- {
- public Location Location;
- public Dictionary<EventRequest, AssemblyMirror> Requests = new Dictionary<EventRequest, AssemblyMirror> ();
- public string LastConditionValue;
- public string FileName;
- public string TypeName;
- }
-
- class DisconnectedException: DebuggerException
- {
- public DisconnectedException (Exception ex):
- base ("The connection with the debugger has been lost. The target application may have exited.", ex)
- {
- }
- }
-
- class DebugSocketException: DebuggerException
- {
- public DebugSocketException (Exception ex):
- base ("Could not open port for debugger. Another process may be using the port.", ex)
- {
- }
- }
-
- class ConnectionException : DebuggerException
- {
- public ConnectionException (Exception ex):
- base ("Could not connect to the debugger.", ex)
- {
- }
- }
-}
+ try {
+ var alreadyVisted = new HashSet<string> ();
+
+ while (true) {
+ if (alreadyVisted.Contains (path))
+ return string.Empty;
+
+ alreadyVisted.Add (path);
+
+ var linkInfo = new Mono.Unix.UnixSymbolicLinkInfo (path);
+ if (linkInfo.IsSymbolicLink && linkInfo.HasContents) {
+ string contentsPath = linkInfo.ContentsPath;
+
+ if (!Path.IsPathRooted (contentsPath))
+ path = Path.Combine (Path.GetDirectoryName (path), contentsPath);
+ else
+ path = contentsPath;
+
+ path = ResolveFullPath (path);
+ continue;
+ }
+
+ path = Path.Combine (ResolveSymbolicLink (Path.GetDirectoryName (path)), Path.GetFileName (path));
+
+ return ResolveFullPath (path);
+ }
+ } catch {
+ return path;
+ }
+ }
+
+ static bool PathsAreEqual (string p1, string p2)
+ {
+ if (string.IsNullOrWhiteSpace (p1) || string.IsNullOrWhiteSpace (p2))
+ return false;
+
+ if (PathComparer.Compare (p1, p2) == 0)
+ return true;
+
+ var rp1 = ResolveSymbolicLink (p1);
+ var rp2 = ResolveSymbolicLink (p2);
+
+ return PathComparer.Compare (rp1, rp2) == 0;
+ }
+
+ Location FindLocationByMethod (MethodMirror method, string file, int line, int column, ref bool insideTypeRange)
+ {
+ int rangeFirstLine = int.MaxValue;
+ int rangeLastLine = -1;
+ Location target = null;
+
+ //Console.WriteLine ("FindLocationByMethod: method = {0}, file = {1}, line = {2}, column = {3}", method.Name, Path.GetFileName (file), line, column);
+
+ foreach (var location in method.Locations) {
+ var srcFile = location.SourceFile;
+
+ //Console.WriteLine ("\tChecking location {0}:{1},{2}...", Path.GetFileName(srcFile), location.LineNumber, location.ColumnNumber);
+
+ //Check if file names match
+ if (srcFile != null && PathComparer.Compare (Path.GetFileName (NormalizePath (srcFile)), Path.GetFileName (file)) == 0) {
+ //Check if full path match(we don't care about md5 if full path match):
+ //1. For backward compatibility
+ //2. If full path matches user himself probably modified code and is aware of modifications
+ //OR if md5 match, useful for alternative location files with breakpoints
+ if (!PathsAreEqual (NormalizePath (srcFile), file) && !SourceLocation.CheckFileHash (file, location.SourceFileHash))
+ continue;
+
+ if (location.LineNumber < rangeFirstLine)
+ rangeFirstLine = location.LineNumber;
+
+ if (location.LineNumber > rangeLastLine)
+ rangeLastLine = location.LineNumber;
+
+ if (line >= rangeFirstLine && line <= rangeLastLine)
+ insideTypeRange = true;
+
+ if (line == location.LineNumber && (column <= 1 || column == location.ColumnNumber)) {
+ if (target != null) {
+ if (location.ILOffset < target.ILOffset)
+ target = location;
+ } else {
+ target = location;
+ }
+ }
+ } else {
+ rangeFirstLine = int.MaxValue;
+ rangeLastLine = -1;
+ }
+ }
+
+ return target;
+ }
+
+ Location FindLocationByType (TypeMirror type, string file, int line, int column, out bool genericMethod, out bool insideTypeRange)
+ {
+ var methodInsideTypeRange = false;
+ Location target = null;
+
+ insideTypeRange = false;
+ genericMethod = false;
+
+ foreach (var method in type.GetMethods ()) {
+ Location location = null;
+
+ if ((location = FindLocationByMethod (method, file, line, column, ref methodInsideTypeRange)) != null) {
+ insideTypeRange |= methodInsideTypeRange;//If any method returns true return true
+
+ if (target != null) {
+ // Use the location with the lowest ILOffset
+ if (location.ILOffset < target.ILOffset) {
+ genericMethod = IsGenericMethod (method);
+ target = location;
+ }
+ } else {
+ genericMethod = IsGenericMethod (method);
+ target = location;
+ }
+ }
+ }
+
+ return target;
+ }
+
+ void ResolvePendingBreakpoint (BreakInfo bi, Location l)
+ {
+ bi.Location = l;
+ InsertBreakpoint ((Breakpoint) bi.BreakEvent, bi);
+ bi.SetStatus (BreakEventStatus.Bound, null);
+ }
+
+ void ResolvePendingCatchpoint (BreakInfo bi, TypeMirror type)
+ {
+ InsertCatchpoint ((Catchpoint) bi.BreakEvent, bi, type);
+ bi.SetStatus (BreakEventStatus.Bound, null);
+ }
+
+ bool UpdateAssemblyFilters (AssemblyMirror asm)
+ {
+ var name = asm.GetName ().FullName;
+ bool found = false;
+ if (userAssemblyNames != null) {
+ //HACK: not sure how else to handle xsp-compiled pages
+ if (name.StartsWith ("App_", StringComparison.Ordinal)) {
+ found = true;
+ } else {
+ foreach (var n in userAssemblyNames) {
+ if (n == name) {
+ found = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if (found) {
+ assemblyFilters.Add (asm);
+ return true;
+ }
+
+ return false;
+ }
+
+ internal void WriteDebuggerOutput (bool isError, string msg)
+ {
+ OnDebuggerOutput (isError, msg);
+ }
+
+ protected override void OnSetActiveThread (long processId, long threadId)
+ {
+ }
+
+ protected override void OnStepInstruction ()
+ {
+ Step (StepDepth.Into, StepSize.Min);
+ }
+
+ protected override void OnStepLine ()
+ {
+ Step (StepDepth.Into, StepSize.Line);
+ }
+
+ protected override void OnStop ()
+ {
+ vm.Suspend ();
+
+ //emit a stop event at the current position of the most recent thread
+ //we use "getprocesses" instead of "ongetprocesses" because it attaches the process to the session
+ //using private Mono.Debugging API, so our thread/backtrace calls will cache stuff that will get used later
+ var process = GetProcesses () [0];
+ EnsureRecentThreadIsValid (process);
+ current_thread = recent_thread;
+ OnTargetEvent (new TargetEventArgs (TargetEventType.TargetStopped) {
+ Process = process,
+ Thread = GetThread (process, recent_thread),
+ Backtrace = GetThreadBacktrace (recent_thread)});
+ }
+
+ void EnsureRecentThreadIsValid (ProcessInfo process)
+ {
+ var infos = process.GetThreads ();
+
+ if (ThreadIsAlive (recent_thread) && HasUserFrame (GetId (recent_thread), infos))
+ return;
+
+ var threads = vm.GetThreads ();
+ foreach (var thread in threads) {
+ if (ThreadIsAlive (thread) && HasUserFrame (GetId (thread), infos)) {
+ recent_thread = thread;
+ return;
+ }
+ }
+ recent_thread = threads[0];
+ }
+
+ long GetId (ThreadMirror thread)
+ {
+ long id;
+ if (!localThreadIds.TryGetValue (thread.ThreadId, out id)) {
+ id = localThreadIds.Count + 1;
+ localThreadIds [thread.ThreadId] = id;
+ }
+ return id;
+ }
+
+ static bool ThreadIsAlive (ThreadMirror thread)
+ {
+ if (thread == null)
+ return false;
+ ThreadState state;
+ try {
+ state = thread.ThreadState;
+ } catch (ObjectCollectedException) {
+ return false;//Thread was already collected by garbage collector, hence it's not alive
+ }
+ return state != ThreadState.Stopped && state != ThreadState.Aborted;
+ }
+
+ //we use the Mono.Debugging classes because they are cached
+ static bool HasUserFrame (long tid, ThreadInfo[] threads)
+ {
+ foreach (var thread in threads) {
+ if (thread.Id != tid)
+ continue;
+
+ var bt = thread.Backtrace;
+ for (int i = 0; i < bt.FrameCount; i++) {
+ var frame = bt.GetFrame (i);
+ if (frame != null && !frame.IsExternalCode)
+ return true;
+ }
+
+ return false;
+ }
+
+ return false;
+ }
+
+ public bool IsExternalCode (StackFrame frame)
+ {
+ return frame.Method == null || string.IsNullOrEmpty (frame.FileName)
+ || (assemblyFilters != null && !assemblyFilters.Contains (frame.Method.DeclaringType.Assembly));
+ }
+
+ public bool IsExternalCode (TypeMirror type)
+ {
+ return assemblyFilters != null && !assemblyFilters.Contains (type.Assembly);
+ }
+
+ protected override AssemblyLine[] OnDisassembleFile (string file)
+ {
+ List<TypeMirror> mirrors;
+
+ if (!source_to_type.TryGetValue (file, out mirrors))
+ return new AssemblyLine [0];
+
+ var lines = new List<AssemblyLine> ();
+ foreach (var type in mirrors) {
+ foreach (var method in type.GetMethods ()) {
+ string srcFile = method.SourceFile != null ? NormalizePath (method.SourceFile) : null;
+
+ if (srcFile == null || !PathsAreEqual (srcFile, file))
+ continue;
+
+ var body = method.GetMethodBody ();
+ int lastLine = -1;
+ int firstPos = lines.Count;
+ string addrSpace = method.FullName;
+
+ foreach (var ins in body.Instructions) {
+ var loc = method.LocationAtILOffset (ins.Offset);
+
+ if (loc != null && lastLine == -1) {
+ lastLine = loc.LineNumber;
+ for (int n = firstPos; n < lines.Count; n++) {
+ AssemblyLine old = lines [n];
+ lines [n] = new AssemblyLine (old.Address, old.AddressSpace, old.Code, loc.LineNumber);
+ }
+ }
+
+ lines.Add (new AssemblyLine (ins.Offset, addrSpace, Disassemble (ins), loc != null ? loc.LineNumber : lastLine));
+ }
+ }
+ }
+
+ lines.Sort (delegate (AssemblyLine a1, AssemblyLine a2) {
+ int res = a1.SourceLine.CompareTo (a2.SourceLine);
+
+ return res != 0 ? res : a1.Address.CompareTo (a2.Address);
+ });
+
+ return lines.ToArray ();
+ }
+
+ public AssemblyLine[] Disassemble (StackFrame frame)
+ {
+ var body = frame.Method.GetMethodBody ();
+ var instructions = body.Instructions;
+ var lines = new List<AssemblyLine> ();
+
+ foreach (var instruction in instructions) {
+ var location = frame.Method.LocationAtILOffset (instruction.Offset);
+ int lineNumber = location != null ? location.LineNumber : -1;
+ var code = Disassemble (instruction);
+
+ lines.Add (new AssemblyLine (instruction.Offset, frame.Method.FullName, code, lineNumber));
+ }
+
+ return lines.ToArray ();
+ }
+
+ public AssemblyLine[] Disassemble (StackFrame frame, int firstLine, int count)
+ {
+ var body = frame.Method.GetMethodBody ();
+ var instructions = body.Instructions;
+ ILInstruction current = null;
+
+ foreach (var instruction in instructions) {
+ if (instruction.Offset >= frame.ILOffset) {
+ current = instruction;
+ break;
+ }
+ }
+
+ if (current == null)
+ return new AssemblyLine [0];
+
+ var lines = new List<AssemblyLine> ();
+
+ while (firstLine < 0 && count > 0) {
+ if (current.Previous == null) {
+ lines.Add (AssemblyLine.OutOfRange);
+ firstLine = 0;
+ break;
+ }
+
+ current = current.Previous;
+ firstLine++;
+ }
+
+ while (current != null && firstLine > 0) {
+ current = current.Next;
+ firstLine--;
+ }
+
+ while (count > 0) {
+ if (current != null) {
+ var location = frame.Method.LocationAtILOffset (current.Offset);
+ int lineNumber = location != null ? location.LineNumber : -1;
+ var code = Disassemble (current);
+
+ lines.Add (new AssemblyLine (current.Offset, frame.Method.FullName, code, lineNumber));
+ current = current.Next;
+ } else {
+ lines.Add (AssemblyLine.OutOfRange);
+ }
+
+ count--;
+ }
+
+ return lines.ToArray ();
+ }
+
+ public void AddSourceUpdate (string fileName)
+ {
+ sourceUpdates.Add (new SourceUpdate(fileName));
+ }
+ public void AddLineUpdate (int oldLine, int newLine)
+ {
+ sourceUpdates.Last().LineUpdates.Add (new Tuple<int, int>(oldLine, newLine));
+ }
+ public void ApplyChanges (ModuleMirror module, byte[] metadataDelta, byte[] ilDelta, byte[] pdbDelta = null)
+ {
+ var rootDomain = VirtualMachine.RootDomain;
+ var metadataArray = rootDomain.CreateByteArray (metadataDelta);
+ var ilArray = rootDomain.CreateByteArray (ilDelta);
+ Value pdbArray;
+ if (pdbDelta == null)
+ pdbArray = VirtualMachine.CreateValue (null);
+ else
+ pdbArray = rootDomain.CreateByteArray (pdbDelta);
+
+ module.Assembly.ApplyChanges_DebugInformation (metadataDelta, pdbDelta);
+ module.ApplyChanges (metadataArray, ilArray, pdbArray);
+ foreach (var sourceUpdate in sourceUpdates)
+ {
+ var types = VirtualMachine.GetTypesForSourceFile (sourceUpdate.FileName, false);
+ foreach (var type in types)
+ {
+ type.ApplySourceChanges (sourceUpdate);
+ }
+ }
+ sourceUpdates.Clear ();
+ }
+
+ public string GetEnCCapabilities()
+ {
+ return vm.GetEnCCapabilities ();
+ }
+ static string EscapeString (string text)
+ {
+ var escaped = new StringBuilder ();
+
+ escaped.Append ('"');
+ for (int i = 0; i < text.Length; i++) {
+ char c = text[i];
+ string txt;
+ switch (c) {
+ case '"': txt = "\\\""; break;
+ case '\0': txt = @"\0"; break;
+ case '\\': txt = @"\\"; break;
+ case '\a': txt = @"\a"; break;
+ case '\b': txt = @"\b"; break;
+ case '\f': txt = @"\f"; break;
+ case '\v': txt = @"\v"; break;
+ case '\n': txt = @"\n"; break;
+ case '\r': txt = @"\r"; break;
+ case '\t': txt = @"\t"; break;
+ default:
+ if (char.GetUnicodeCategory (c) == UnicodeCategory.OtherNotAssigned) {
+ escaped.AppendFormat ("\\u{0:X4}", c);
+ } else {
+ escaped.Append (c);
+ }
+ continue;
+ }
+ escaped.Append (txt);
+ }
+ escaped.Append ('"');
+
+ return escaped.ToString ();
+ }
+
+ static string Disassemble (ILInstruction ins)
+ {
+ string oper;
+ if (ins.Operand is MethodMirror)
+ oper = ((MethodMirror)ins.Operand).FullName;
+ else if (ins.Operand is FieldInfoMirror)
+ oper = ((FieldInfoMirror)ins.Operand).FullName;
+ else if (ins.Operand is TypeMirror)
+ oper = ((TypeMirror)ins.Operand).FullName;
+ else if (ins.Operand is ILInstruction)
+ oper = ((ILInstruction)ins.Operand).Offset.ToString ("x8");
+ else if (ins.Operand is string)
+ oper = EscapeString ((string) ins.Operand);
+ else if (ins.Operand == null)
+ oper = string.Empty;
+ else
+ oper = ins.Operand.ToString ();
+
+ return ins.OpCode + " " + oper;
+ }
+
+ readonly static bool IsWindows;
+ readonly static bool IsMac;
+ readonly static StringComparer PathComparer;
+
+ static bool IgnoreFilenameCase {
+ get { return IsMac || IsWindows; }
+ }
+
+ static SoftDebuggerSession ()
+ {
+ IsWindows = Path.DirectorySeparatorChar == '\\';
+ IsMac = !IsWindows && IsRunningOnMac ();
+ PathComparer = IgnoreFilenameCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal;
+ ThreadMirror.NativeTransitions = true;
+ }
+
+ //From Managed.Windows.Forms/XplatUI
+ static bool IsRunningOnMac ()
+ {
+ IntPtr buf = IntPtr.Zero;
+ try {
+ buf = Marshal.AllocHGlobal (8192);
+ // This is a hacktastic way of getting sysname from uname ()
+ if (uname (buf) == 0) {
+ string os = Marshal.PtrToStringAnsi (buf);
+ if (os == "Darwin")
+ return true;
+ }
+ } catch {
+ return false;
+ } finally {
+ if (buf != IntPtr.Zero)
+ Marshal.FreeHGlobal (buf);
+ }
+ return false;
+ }
+
+ [System.Runtime.InteropServices.DllImport ("libc")]
+ static extern int uname (IntPtr buf);
+ }
+
+ class LocationComparer : IComparer<Location>
+ {
+ public int Compare (Location loc0, Location loc1)
+ {
+ if (loc0.LineNumber < loc1.LineNumber)
+ return -1;
+ if (loc0.LineNumber > loc1.LineNumber)
+ return 1;
+
+ if (loc0.ColumnNumber < loc1.ColumnNumber)
+ return -1;
+ if (loc0.ColumnNumber > loc1.ColumnNumber)
+ return 1;
+
+ return loc0.ILOffset - loc1.ILOffset;
+ }
+ }
+
+ class BreakInfo: BreakEventInfo
+ {
+ public Location Location;
+ public Dictionary<EventRequest, AssemblyMirror> Requests = new Dictionary<EventRequest, AssemblyMirror> ();
+ public string LastConditionValue;
+ public string FileName;
+ public string TypeName;
+ }
+
+ class DisconnectedException: DebuggerException
+ {
+ public DisconnectedException (Exception ex):
+ base ("The connection with the debugger has been lost. The target application may have exited.", ex)
+ {
+ }
+ }
+
+ class DebugSocketException: DebuggerException
+ {
+ public DebugSocketException (Exception ex):
+ base ("Could not open port for debugger. Another process may be using the port.", ex)
+ {
+ }
+ }
+
+ class ConnectionException : DebuggerException
+ {
+ public ConnectionException (Exception ex):
+ base ("Could not connect to the debugger.", ex)
+ {
+ }
+ }
+}
diff --git a/Mono.Debugging/Mono.Debugging.Client/Assembly.cs b/Mono.Debugging/Mono.Debugging.Client/Assembly.cs
new file mode 100644
index 0000000..cec1481
--- /dev/null
+++ b/Mono.Debugging/Mono.Debugging.Client/Assembly.cs
@@ -0,0 +1,84 @@
+//
+// Assembly.cs
+//
+// Author:
+// Jonathan Chang <t-jochang@microsoft.com>
+//
+// Copyright (c) 2022
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+using System;
+namespace Mono.Debugging.Client
+{
+ public class Assembly
+ {
+ public Assembly (string name, string path, bool optimized, bool userCode, string symbolStatus, string symbolFile, int? order, string version, string timestamp, string address, string process, string appdomain, long? processId)
+ {
+ Name = name;
+ Path = path;
+ Optimized = optimized;
+ SymbolStatus = symbolStatus;
+ SymbolFile = symbolFile;
+ if (order == null) {
+ Order = -1;
+ } else {
+ Order = (int)order;
+ }
+
+ TimeStamp = timestamp;
+ Address = address;
+ Process = process;
+ AppDomain = appdomain;
+ Version = version;
+ UserCode = userCode;
+ ProcessId = processId;
+
+ }
+ public Assembly (string path)
+ {
+ Path = path;
+ }
+
+ public string Name { get; private set; }
+
+ public string Path { get; private set; }
+
+ public bool Optimized { get; private set; }
+
+ public bool UserCode { get; private set; }
+
+ public string SymbolStatus { get; private set; }
+
+ public string SymbolFile { get; private set; }
+
+ public int Order { get; private set; }
+
+ public String Version { get; private set; }
+
+ public string TimeStamp { get; private set; }
+
+ public string Address { get; private set; }
+
+ public string Process { get; private set; }
+
+ public string AppDomain { get; private set; }
+
+ public long? ProcessId { get; private set; } = -1;
+ }
+} \ No newline at end of file
diff --git a/Mono.Debugging/Mono.Debugging.Client/AssemblyEventArgs.cs b/Mono.Debugging/Mono.Debugging.Client/AssemblyEventArgs.cs
index 7d119f2..f149ab6 100644
--- a/Mono.Debugging/Mono.Debugging.Client/AssemblyEventArgs.cs
+++ b/Mono.Debugging/Mono.Debugging.Client/AssemblyEventArgs.cs
@@ -30,13 +30,18 @@ namespace Mono.Debugging.Client
{
public class AssemblyEventArgs : EventArgs
{
- public AssemblyEventArgs (string location)
+ public AssemblyEventArgs (Assembly assembly)
{
- Location = location;
+ Location = assembly.Path;
+ Assembly = assembly;
}
public string Location {
get; private set;
}
+
+ public Assembly Assembly {
+ get; private set;
+ }
}
}
diff --git a/Mono.Debugging/Mono.Debugging.Client/BreakEventInfo.cs b/Mono.Debugging/Mono.Debugging.Client/BreakEventInfo.cs
index 074d098..dfda575 100644
--- a/Mono.Debugging/Mono.Debugging.Client/BreakEventInfo.cs
+++ b/Mono.Debugging/Mono.Debugging.Client/BreakEventInfo.cs
@@ -52,6 +52,8 @@ namespace Mono.Debugging.Client
/// </summary>
public BreakEventStatus Status { get; private set; }
+ public Breakpoint Breakpoint { get; set; }
+
/// <summary>
/// Gets a description of the status
/// </summary>
diff --git a/Mono.Debugging/Mono.Debugging.Client/Breakpoint.cs b/Mono.Debugging/Mono.Debugging.Client/Breakpoint.cs
index 8770b1d..f201f2c 100644
--- a/Mono.Debugging/Mono.Debugging.Client/Breakpoint.cs
+++ b/Mono.Debugging/Mono.Debugging.Client/Breakpoint.cs
@@ -117,7 +117,9 @@ namespace Mono.Debugging.Client
ResetAdjustedColumn ();
column = newColumn;
}
-
+
+ public bool UpdatedByEnC { get; set; }
+
public void SetLine (int newLine)
{
ResetAdjustedLine ();
diff --git a/Mono.Debugging/Mono.Debugging.Client/DebuggerSession.cs b/Mono.Debugging/Mono.Debugging.Client/DebuggerSession.cs
index c6a0fee..333bb46 100644
--- a/Mono.Debugging/Mono.Debugging.Client/DebuggerSession.cs
+++ b/Mono.Debugging/Mono.Debugging.Client/DebuggerSession.cs
@@ -32,6 +32,7 @@ using System.Collections.Generic;
using Mono.Debugging.Backend;
using Mono.Debugging.Evaluation;
+using System.Linq;
namespace Mono.Debugging.Client
{
@@ -49,6 +50,7 @@ namespace Mono.Debugging.Client
{
readonly Dictionary<BreakEvent, BreakEventInfo> breakpoints = new Dictionary<BreakEvent, BreakEventInfo> ();
readonly Dictionary<string, string> resolvedExpressionCache = new Dictionary<string, string> ();
+ private readonly List<Assembly> assemblies = new List<Assembly> ();
readonly InternalDebuggerSession frontend;
readonly object slock = new object ();
readonly object breakpointStoreLock = new object ();
@@ -301,6 +303,25 @@ namespace Mono.Debugging.Client
}
/// <summary>
+ /// Gets assemblies from the debugger session.
+ /// </summary>
+ public Assembly[] GetAssemblies() {
+ lock (assemblies) {
+ return assemblies.ToArray ();
+ }
+ }
+
+ /// <summary>
+ /// Gets assemblies from the debugger session but filter by the specific process ID .
+ /// </summary>
+ internal Assembly[] GetAssemblies(long processId)
+ {
+ lock (assemblies) {
+ return assemblies.Where (a => a.ProcessId == processId).ToArray ();
+ }
+ }
+
+ /// <summary>
/// Gets or sets the breakpoint store for the debugger session.
/// </summary>
public BreakpointStore Breakpoints {
@@ -1302,9 +1323,23 @@ namespace Mono.Debugging.Client
internal protected void OnAssemblyLoaded (string assemblyLocation)
{
- AssemblyLoaded?.Invoke (this, new AssemblyEventArgs (assemblyLocation));
+ var assembly = new Assembly (assemblyLocation);
+ lock (assemblies) {
+ assemblies.Add (assembly);
+ }
+
+ AssemblyLoaded?.Invoke (this, new AssemblyEventArgs (assembly));
}
-
+
+ internal protected void OnAssemblyLoaded (Assembly assembly)
+ {
+ lock (assemblies) {
+ assemblies.Add (assembly);
+ }
+
+ AssemblyLoaded?.Invoke (this, new AssemblyEventArgs (assembly));
+ }
+
internal protected void SetBusyState (BusyStateEventArgs args)
{
BusyStateChanged?.Invoke (this, args);
diff --git a/Mono.Debugging/Mono.Debugging.Client/ProcessInfo.cs b/Mono.Debugging/Mono.Debugging.Client/ProcessInfo.cs
index 99c80f5..5ba023a 100644
--- a/Mono.Debugging/Mono.Debugging.Client/ProcessInfo.cs
+++ b/Mono.Debugging/Mono.Debugging.Client/ProcessInfo.cs
@@ -78,5 +78,13 @@ namespace Mono.Debugging.Client
{
return session.GetThreads (id);
}
+
+ /// <summary>
+ /// Gets assemblies from the debugger session that matches the process ID.
+ /// </summary>
+ public Assembly[] GetAssemblies ()
+ {
+ return session.GetAssemblies (id);
+ }
}
}