pascalabcnet/VisualPascalABCNETLinux/MonoDebugging/Soft/SoftDebuggerSession.cs
AlexanderZemlyak 0c29ac92b2
Refactoring of Compiler.cs (#2984)
* 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>
2023-12-18 22:33:27 +03:00

3582 lines
110 KiB
C#
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 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)
{
}
}
}