pascalabcnet/VisualPascalABCNETLinux/MonoDebugging/Soft/SoftDebuggerSession.cs

3582 lines
110 KiB
C#
Raw Normal View History

2023-04-10 22:04:27 +03:00
//
// 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,
2023-04-10 22:04:27 +03:00
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true,
};
#if (DEBUG)
Console.WriteLine(psi.Arguments);
#endif
2023-04-10 22:04:27 +03:00
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();
}
2023-04-10 22:04:27 +03:00
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();
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
var exe = (string) startInfo.GetChild ("file_name", evalOptions).GetRawValue (evalOptions);
2023-04-10 22:04:27 +03:00
// 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;
}
2023-04-10 22:04:27 +03:00
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;
}
2023-06-18 15:48:32 +03:00
if (breakEvent is FunctionBreakpoint) {
var function = breakEvent as FunctionBreakpoint;
2023-04-10 22:04:27 +03:00
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);
}
2023-06-18 15:48:32 +03:00
} else if (breakEvent is InstructionBreakpoint) {
var instruction = breakEvent as InstructionBreakpoint;
2023-04-10 22:04:27 +03:00
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);
}
2023-06-18 15:48:32 +03:00
} else if (breakEvent is Breakpoint) {
var breakpoint = breakEvent as Breakpoint;
2023-04-10 22:04:27 +03:00
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);
}
2023-06-18 15:48:32 +03:00
} else if (breakEvent is Catchpoint) {
var catchpoint = breakEvent as Catchpoint;
2023-04-10 22:04:27 +03:00
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
2023-04-10 22:04:27 +03:00
//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
2023-04-10 22:04:27 +03:00
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);
}
2023-09-24 12:37:51 +03:00
public void StepOut()
{
Step(StepDepth.Out, StepSize.Line);
}
2023-04-10 22:04:27 +03:00
void Step (StepDepth depth, StepSize size)
{
try {
//Console.WriteLine("Adaptor.CancelAsyncOperations");
2023-04-10 22:04:27 +03:00
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");
2023-04-10 22:04:27 +03:00
OnResumed ();
//Console.WriteLine("vm.Resume");
2023-04-10 22:04:27 +03:00
vm.Resume ();
//Console.WriteLine("DequeueEventsForFirstThread");
2023-04-10 22:04:27 +03:00
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)
{
}
}
}