* Add first comments
* Finish commenting for Compile and CompileUnit
* Write TODO sections
* Add a few clarifications
* splitted some functions from compile
* Written some methods from Compile to functions
* Update variable names
* Refactor - stage 1
Refactor GetUsesSection and IsPossibleNameSpace
* Refactor - stage 2
Rename a few functions and variables
* Correct an inaccuracy
* Added comments, look through CompileUnit
* Rename a few functions and add new comments
* Split CompileUnit to Subfunctions
Add IsUnitCompiled, IsUnitInPCU, InitializeNewUnit, GetSourceCode, GenSyntaxTree, GenUnitDocumentation, CheckDLLDirectiveOnlyForLibraries, MatchErrorsToBadNodes, CheckIfUnitModule, SetUseDLLForSystemUnits,
CompileInterfaceDependencies, CompileCurrentUnitInterface, GetImplementationUsesSection, CompileImplementationDependencies, CompileCurrentUnitImplementation
* Added some TODOs
* Return uses_unit_in original name
Renaming of syntax tree nodes leads to internal compiler errors
* Extract GenerateILCode method
* Add checking if recompilation needed method
Needs to be discussed and revised
* Add functions for catch blocks in Compile
* Extract building semantic tree method
Creating main function to be moved to another class
* Rename UnitsSortedList
* Edit CompileUnitsFromDelayedList method
* Change a few variable names, make CreateRCFile function and add comments
* Make code more "user-friendly"
* Add TODOs 31.11.23
* Create PrebuildSemanticTreeActionsMethod
* Refactor semantic checks section in initialize new unit method
* Refactor Adding standard units to uses method
* Return file_name and compiler_directives original names to avoid internal compilation errors
* Refactor GetReferences Method
* Initial refactoring of IncludeNamespaces function
* Create three more methods and wrap important code in regions
* Add TODO's
* Resolve merge conflicts
* Add returned value to ConstructSyntaxTree method
* Fix UnitsSortedList NotFoundError in PCUWriter
* Workaround commit
* Update PABCSystem after tests' changes
* Rename syntax trees in some methods
* Delete CurrentSyntaxUnit variable
* Revert unnecessary project files changes
* Squashed commit of the following:
commit f4d7599f1a39252feac97bd5391f46dfdc25f6f4
Author: Владислав Крылов <krylov@sfedu.ru>
Date: Wed Nov 22 11:43:29 2023 +0300
added links to identarranger
commit 3bd5d33e2b2969884e61a3279ff9c353013b57fe
Author: Владислав Крылов <krylov@sfedu.ru>
Date: Wed Nov 22 11:43:03 2023 +0300
Change extension for verybasic to yavb
commit 61294c6e7d405e1c68516cc81d3a109e8a1ee295
Author: MovchanGitHub <92666028+MovchanGitHub@users.noreply.github.com>
Date: Tue Nov 21 09:33:57 2023 +0300
Change Program Example
commit 86971488341ed6bf13c39e13a513d7ac0c51c285
Author: MovchanGitHub <92666028+MovchanGitHub@users.noreply.github.com>
Date: Tue Nov 21 09:28:59 2023 +0300
Add IndentArranger to Compile
commit 2ce50bbbf9db22c4243b247b870f6935ce206d26
Author: MovchanGitHub <92666028+MovchanGitHub@users.noreply.github.com>
Date: Fri Nov 17 10:00:31 2023 +0300
Add Semicolon After Each Statement
commit 796309d340e8d8ba730ff9418fa376f34fb7fd36
Author: MovchanGitHub <92666028+MovchanGitHub@users.noreply.github.com>
Date: Wed Nov 15 18:49:11 2023 +0300
Add Alpha Version of Python-style If-statement
commit 5eb88f946fe8254b4f5c5a56ed0a567b3be51227
Merge: ab4ce5b0 0162b637
Author: Владислав Крылов <krylov@sfedu.ru>
Date: Wed Nov 15 16:54:26 2023 +0300
Merge branch 'IndentArranger' into VeryBasicLanguage
commit ab4ce5b0e32d42004726e3dc45cc0d8a65e53f56
Author: Владислав Крылов <krylov@sfedu.ru>
Date: Wed Nov 15 16:53:51 2023 +0300
Fixes from seminar
commit af74012289d0d08517c13499a2a66cb543fc06d2
Author: Владислав Крылов <krylov@sfedu.ru>
Date: Sun Nov 12 18:57:29 2023 +0300
finally working!
fully implemented compatibility
commit 58a39c313324b468d1eec207ba3f5f6eddf74ef2
Author: Владислав Крылов <krylov@sfedu.ru>
Date: Sun Nov 12 11:58:16 2023 +0300
more compatibility with pabc
Now verybasic statements translate to pascal compiler
added .bat script for autobuilding verybasic
commit 0162b6376b8fe970704f26213bf9f0f670dfb12e
Author: MovchanGitHub <92666028+MovchanGitHub@users.noreply.github.com>
Date: Sun Nov 12 10:06:14 2023 +0300
Update test.txt
commit 1b759ab1cd7ee2ffeb14afff0418ddf0f30b0399
Author: MovchanGitHub <92666028+MovchanGitHub@users.noreply.github.com>
Date: Sun Nov 12 10:05:36 2023 +0300
Add Indent and Unindent Keywords to Generated File
commit cabda7f3985651cff2a8740f1a5bdea8fe7ac892
Author: MovchanGitHub <92666028+MovchanGitHub@users.noreply.github.com>
Date: Fri Nov 10 21:25:52 2023 +0300
Add Generation of Output File
commit 306f033d0176ab559e3622b8505427dbe2e0e203
Author: MovchanGitHub <92666028+MovchanGitHub@users.noreply.github.com>
Date: Thu Nov 9 20:42:18 2023 +0300
Update IndentArranger.sln
commit 60c0ceced1aa3a7d2e33de0facb1126e295f43b5
Author: MovchanGitHub <92666028+MovchanGitHub@users.noreply.github.com>
Date: Thu Nov 9 20:41:22 2023 +0300
Add IndentArranger
commit 1f4556c4573dae154fcc167b41ff6ab9e8122136
Author: Владислав Крылов <krylov@sfedu.ru>
Date: Mon Nov 6 22:48:36 2023 +0300
Made my own VeryBasicParser project
Copied some code from SaushkinParser
Tried to compile it and implement into Pascal
Doesn't work due to grammatik issue
* Squashed commit of the following:
commit 4e73d9ac3ffef68312f06a74dc83afffcf3ccbee
Author: Владислав Крылов <krylov@sfedu.ru>
Date: Wed Nov 22 15:08:10 2023 +0300
Fixed GPPG (i think so at least)
commit e5cfc220828b37fb2f35e565683019716ec3f0ce
Author: Владислав Крылов <krylov@sfedu.ru>
Date: Wed Nov 22 11:44:27 2023 +0300
Update Compiler.cs
commit 4698e2e75a5f3b4f523a8bc7733a50e8abb4fca1
Merge: 22aaf2b2 f4d7599f
Author: Владислав Крылов <krylov@sfedu.ru>
Date: Wed Nov 22 11:43:54 2023 +0300
Merge branch 'IndentArrangerTemp' into VeryBasicLanguage
commit f4d7599f1a39252feac97bd5391f46dfdc25f6f4
Author: Владислав Крылов <krylov@sfedu.ru>
Date: Wed Nov 22 11:43:29 2023 +0300
added links to identarranger
commit 3bd5d33e2b2969884e61a3279ff9c353013b57fe
Author: Владислав Крылов <krylov@sfedu.ru>
Date: Wed Nov 22 11:43:03 2023 +0300
Change extension for verybasic to yavb
commit 22aaf2b2508278a9ffce525ffed52419bf72c23f
Merge: c326174f 61294c6e
Author: Владислав Крылов <krylov@sfedu.ru>
Date: Wed Nov 22 11:26:36 2023 +0300
Merge branch 'IndentArrangerTemp' into VeryBasicLanguage
commit 61294c6e7d405e1c68516cc81d3a109e8a1ee295
Author: MovchanGitHub <92666028+MovchanGitHub@users.noreply.github.com>
Date: Tue Nov 21 09:33:57 2023 +0300
Change Program Example
commit 86971488341ed6bf13c39e13a513d7ac0c51c285
Author: MovchanGitHub <92666028+MovchanGitHub@users.noreply.github.com>
Date: Tue Nov 21 09:28:59 2023 +0300
Add IndentArranger to Compile
commit c326174ff5ecccf9c4f76d58aea4653f047af049
Author: Владислав Крылов <krylov@sfedu.ru>
Date: Sun Nov 19 15:03:19 2023 +0300
Added UniversalParserHelper and GPPG
ShiftReduceParser moved to another project
UniversalParserHelper project added, most of it copied from SaushkinParser
commit 2ce50bbbf9db22c4243b247b870f6935ce206d26
Author: MovchanGitHub <92666028+MovchanGitHub@users.noreply.github.com>
Date: Fri Nov 17 10:00:31 2023 +0300
Add Semicolon After Each Statement
commit 796309d340e8d8ba730ff9418fa376f34fb7fd36
Author: MovchanGitHub <92666028+MovchanGitHub@users.noreply.github.com>
Date: Wed Nov 15 18:49:11 2023 +0300
Add Alpha Version of Python-style If-statement
commit 5eb88f946fe8254b4f5c5a56ed0a567b3be51227
Merge: ab4ce5b0 0162b637
Author: Владислав Крылов <krylov@sfedu.ru>
Date: Wed Nov 15 16:54:26 2023 +0300
Merge branch 'IndentArranger' into VeryBasicLanguage
commit ab4ce5b0e32d42004726e3dc45cc0d8a65e53f56
Author: Владислав Крылов <krylov@sfedu.ru>
Date: Wed Nov 15 16:53:51 2023 +0300
Fixes from seminar
commit af74012289d0d08517c13499a2a66cb543fc06d2
Author: Владислав Крылов <krylov@sfedu.ru>
Date: Sun Nov 12 18:57:29 2023 +0300
finally working!
fully implemented compatibility
commit 58a39c313324b468d1eec207ba3f5f6eddf74ef2
Author: Владислав Крылов <krylov@sfedu.ru>
Date: Sun Nov 12 11:58:16 2023 +0300
more compatibility with pabc
Now verybasic statements translate to pascal compiler
added .bat script for autobuilding verybasic
commit 0162b6376b8fe970704f26213bf9f0f670dfb12e
Author: MovchanGitHub <92666028+MovchanGitHub@users.noreply.github.com>
Date: Sun Nov 12 10:06:14 2023 +0300
Update test.txt
commit 1b759ab1cd7ee2ffeb14afff0418ddf0f30b0399
Author: MovchanGitHub <92666028+MovchanGitHub@users.noreply.github.com>
Date: Sun Nov 12 10:05:36 2023 +0300
Add Indent and Unindent Keywords to Generated File
commit cabda7f3985651cff2a8740f1a5bdea8fe7ac892
Author: MovchanGitHub <92666028+MovchanGitHub@users.noreply.github.com>
Date: Fri Nov 10 21:25:52 2023 +0300
Add Generation of Output File
commit 306f033d0176ab559e3622b8505427dbe2e0e203
Author: MovchanGitHub <92666028+MovchanGitHub@users.noreply.github.com>
Date: Thu Nov 9 20:42:18 2023 +0300
Update IndentArranger.sln
commit 60c0ceced1aa3a7d2e33de0facb1126e295f43b5
Author: MovchanGitHub <92666028+MovchanGitHub@users.noreply.github.com>
Date: Thu Nov 9 20:41:22 2023 +0300
Add IndentArranger
commit 1f4556c4573dae154fcc167b41ff6ab9e8122136
Author: Владислав Крылов <krylov@sfedu.ru>
Date: Mon Nov 6 22:48:36 2023 +0300
Made my own VeryBasicParser project
Copied some code from SaushkinParser
Tried to compile it and implement into Pascal
Doesn't work due to grammatik issue
* Changed GPPG project NET Framework version, added .dll to gitignore
* Adding UniversalParserHelper to project, trying to include VeryBasic
* Managed dependencies and got VeryBasicLanguage to work
* Change extension of a test program
* Rebuild changes
What should be added to .gitignore?
* Fix bug related to err0524_res_unit.pas
* Rebuild Parser
* Change Indent and Unindent tokens
* Add Symbol Table to ParserABC.y
* Change Indent Space Number to 2
* Add While Loop
* Add Some Operations to Parser
* Make Initialization Node at the Beginning of a program
* Create Grammar.txt
* Test program added
* Add ELIF and SyntaxHighlight
* Fix TableSymbol
* Add Division
* Add Method Call
* Add For Loop
* Rename SPython Parser Folder
* Add documented comments for CompileUnit method
* Added Errors to SPython
Added Errors.cs
Removed link to PABCSaushkinParser
Minor fixes
* Create default constructor for SourceContext
* Move null check of currentUnit to upper level in CompileUnit
* Move CreateMainFunction method from Compiler to TreeConverter class
* Add gppg and parserhelper to visualpascalabcnet dependencies
* Updated installer files to include GPPG and UniversalParserHelper
* Rename GPPG to ShiftReduceParser
* Fix ShiftReduceParser project dependencies
* Fix ShiftReduceParserDependencies second iteration
* Workaround commit
* Update PABCSystem after tests' changes
* Refactor ConvertDirectives method
* Get rid of legacy standard modules code
* Return varBeginOffset and beginOffset calculation to Compiler class
Размещение метода в SyntaxTreeToSemanticTreeConverter не целесообразно. В комментарии видимо имелось в виду что-то другое.
* Workaround commit
* Update PABCSystem after tests' changes
* Revert SPython changes
Оставляем только изменения связанные с рефакторингом.
* Update .gitignore
Co-authored-by: Sun Serega <sunserega2@gmail.com>
* Resolve a few Sun Serega treds
* Delete comments in ParsersController.cs
* Replace specific path with path variable in Studio.bat
* Add comment in Studio.bat file and return FileName in CompilerError.cs
* Fix TreeSubsidiary.cs encoding and sectCore.nsh indents
* Return old version of TestRunner.exe
* Rename some variables and polish a few methods
* Uncomment accidentally commented code
* Replace spaces with tabs
* Changed dll name from GPPG
* Revert "Changed dll name from GPPG"
This reverts commit c485cc8cb787809b7e9dfa8a361e75f17ed39893.
* Update .gitignore
* Delete Libraries/ShiftReduceParser.dll
* Delete bin\ShiftReduceParser.dll
* Replace tabs with spaces
* Update encoding in Studio.bat
* Fix bug with PABCrtl excluded files
* Refactor StandardModule class
* Add null checks to make debuging easier
* Delete unnecessary null checks in SymTable.cs
---------
Co-authored-by: Владислав Крылов <krylov@sfedu.ru>
Co-authored-by: MovchanGitHub <92666028+MovchanGitHub@users.noreply.github.com>
Co-authored-by: Sun Serega <sunserega2@gmail.com>
3582 lines
110 KiB
C#
3582 lines
110 KiB
C#
//
|
||
// 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 Mono.Debugger.Soft;
|
||
|
||
using Mono.Debugging.Client;
|
||
using Mono.Debugging.Evaluation;
|
||
|
||
using StackFrame = Mono.Debugger.Soft.StackFrame;
|
||
using Assembly = Mono.Debugging.Client.Assembly;
|
||
|
||
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<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;
|
||
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;
|
||
List<SourceUpdate> sourceUpdates;
|
||
Mono.Debugging.Client.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;
|
||
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,
|
||
RedirectStandardInput = true,
|
||
RedirectStandardError = true,
|
||
UseShellExecute = false,
|
||
CreateNoWindow = true,
|
||
};
|
||
#if (DEBUG)
|
||
Console.WriteLine(psi.Arguments);
|
||
#endif
|
||
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);
|
||
startArgs = null;
|
||
} else {
|
||
VirtualMachineManager.CancelConnection(connection);
|
||
}
|
||
} 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));
|
||
}
|
||
}
|
||
|
||
public void ForceEndSession()
|
||
{
|
||
EndSession();
|
||
}
|
||
|
||
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 ("file_name", 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;
|
||
}
|
||
|
||
public void AddOrReplaceSymbolPathMap (string assembly, string path)
|
||
{
|
||
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)
|
||
{
|
||
var vm_types = vm.GetTypes(fullName, true);
|
||
foreach (var vm_type in vm_types)
|
||
ProcessType(vm_type);
|
||
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 (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 (Mono.Debugging.Client.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 Mono.Debugging.Client.ThreadInfo[] OnGetThreads (long processId)
|
||
{
|
||
if (current_threads == null) {
|
||
var mirrors = vm.GetThreads ();
|
||
var threads = new Mono.Debugging.Client.ThreadInfo[mirrors.Count];
|
||
|
||
for (int i = 0; i < mirrors.Count; i++) {
|
||
var thread = mirrors[i];
|
||
|
||
threads[i] = new Mono.Debugging.Client.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;
|
||
}
|
||
|
||
Mono.Debugging.Client.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) {
|
||
var function = breakEvent as FunctionBreakpoint;
|
||
foreach (var method in FindMethodsByName (function.TypeName, function.MethodName, function.ParamTypes)) {
|
||
if (!ResolveFunctionBreakpoint (breakInfo, function, method)) {
|
||
breakInfo.SetStatus (BreakEventStatus.NotBound, null);
|
||
}
|
||
}
|
||
|
||
breakInfo.TypeName = function.TypeName;
|
||
|
||
lock (pending_bes) {
|
||
pending_bes.Add (breakInfo);
|
||
}
|
||
} else if (breakEvent is InstructionBreakpoint) {
|
||
var instruction = breakEvent as InstructionBreakpoint;
|
||
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) {
|
||
var breakpoint = breakEvent as 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) {
|
||
var catchpoint = breakEvent as 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)
|
||
{
|
||
req.Key.Disable ();
|
||
var request = vm.SetBreakpoint (bi.Location.Method, bi.Location.ILOffset);
|
||
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;
|
||
|
||
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;
|
||
}
|
||
|
||
#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;
|
||
|
||
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 (outputOptions.ExceptionMessage)
|
||
OnDebuggerOutput (false, string.Format ("Error while parsing type ‘{0}’.\n", exceptionType));
|
||
}
|
||
}
|
||
}
|
||
|
||
if (types.TryGetValue(exceptionType, out var typemirrors)) {
|
||
var exception = typemirrors.First();
|
||
var request = vm.CreateExceptionRequest (exception, caught, true, false);
|
||
request.Enable();
|
||
exceptionRequests[exceptionType] = request;
|
||
}
|
||
}
|
||
}
|
||
|
||
public void EnableOtherExceptions()
|
||
{
|
||
if (!vm.Version.AtLeast(2, 54))
|
||
return;
|
||
|
||
if (otherExceptions == null)
|
||
otherExceptions = vm.CreateExceptionRequest (null, true, true, true);
|
||
otherExceptions.Enable ();
|
||
}
|
||
|
||
public void DisableException(string exceptionType, bool setuncaught = true)
|
||
{
|
||
lock (exceptionRequests) {
|
||
if (exceptionRequests.TryGetValue (exceptionType, out var request)) {
|
||
request.Disable();
|
||
exceptionRequests.Remove(exceptionType);
|
||
}
|
||
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 typeName, string methodName, string[] paramTypes)
|
||
{
|
||
if (!started)
|
||
yield break;
|
||
|
||
if (vm.Version.AtLeast (2, 9)) {
|
||
// 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, true)) {
|
||
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);
|
||
}
|
||
|
||
public void StepOut()
|
||
{
|
||
Step(StepDepth.Out, StepSize.Line);
|
||
}
|
||
|
||
void Step (StepDepth depth, StepSize size)
|
||
{
|
||
try {
|
||
//Console.WriteLine("Adaptor.CancelAsyncOperations");
|
||
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;
|
||
//Console.WriteLine("OnResumed");
|
||
OnResumed ();
|
||
//Console.WriteLine("vm.Resume");
|
||
vm.Resume ();
|
||
//Console.WriteLine("DequeueEventsForFirstThread");
|
||
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) {
|
||
if (outputOptions.ExceptionMessage)
|
||
OnDebuggerOutput(true, string.Format ("Step request failed: {0}", ex.Message));
|
||
DebuggerLoggingService.LogError ("Step request failed", ex);
|
||
}
|
||
}
|
||
|
||
private StepFilter ShouldFilterStaticCtor()
|
||
{
|
||
var frames = current_thread.GetFrames();
|
||
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);
|
||
}
|
||
}
|
||
}
|
||
|
||
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");
|
||
|
||
HandleAssemblyLoaded (asm);
|
||
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));
|
||
}
|
||
}
|
||
|
||
private void HandleAssemblyLoaded (AssemblyMirror asm)
|
||
{
|
||
var name = asm.GetName ();
|
||
var assemblyObject = asm.GetAssemblyObject ();
|
||
bool isDynamic = asm.IsDynamic;
|
||
string assemblyName;
|
||
bool hasSymbol;
|
||
if (!isDynamic) {
|
||
assemblyName = name.Name;
|
||
hasSymbol = asm.HasDebugInfo;
|
||
} else {
|
||
assemblyName = string.Empty;
|
||
hasSymbol = false;
|
||
}
|
||
var assembly = new Assembly (
|
||
assemblyName,
|
||
asm?.Location ?? string.Empty,
|
||
true,
|
||
hasSymbol,
|
||
string.Empty,
|
||
string.Empty,
|
||
-1,
|
||
name?.Version?.Major.ToString () ?? string.Empty,
|
||
// TODO: module time stamp
|
||
string.Empty,
|
||
assemblyObject?.Address.ToString () ?? string.Empty,
|
||
string.Format ("[{0}]{1}", asm?.VirtualMachine?.TargetProcess?.Id ?? -1, asm?.VirtualMachine?.TargetProcess?.ProcessName ?? string.Empty),
|
||
asm?.Domain?.FriendlyName ?? string.Empty,
|
||
asm?.VirtualMachine?.TargetProcess?.Id ?? -1,
|
||
hasSymbol,
|
||
isDynamic
|
||
);
|
||
|
||
OnAssemblyLoaded (assembly);
|
||
}
|
||
|
||
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 Mono.Debugging.Client.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 Mono.Debugging.Client.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 Mono.Debugging.Client.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) //add new methods to type
|
||
{
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
|
||
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.TypeName, bp.MethodName, 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;
|
||
}*/
|
||
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, Mono.Debugging.Client.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)
|
||
{
|
||
}
|
||
}
|
||
}
|