diff options
author | Jonathan CHang <jonathan34c@gmail.com> | 2022-07-06 21:25:43 +0300 |
---|---|---|
committer | Jonathan CHang <jonathan34c@gmail.com> | 2022-07-06 21:25:43 +0300 |
commit | e1bd562e518e9fa6943b4c4f562a07376ad5499e (patch) | |
tree | a2f25cc6d58f4c9443d0460d327f732c98f1a3ef | |
parent | 7cc29ac19969ffdda3844efd8f9984c90df7430e (diff) |
clean failed unit test codedev/t-jochang/moduleSafe
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); + } } } |