975 lines
39 KiB
C#
975 lines
39 KiB
C#
![]() |
/*
|
||
|
Copyright (C) 2014-2019 de4dot@gmail.com
|
||
|
|
||
|
This file is part of dnSpy
|
||
|
|
||
|
dnSpy is free software: you can redistribute it and/or modify
|
||
|
it under the terms of the GNU General Public License as published by
|
||
|
the Free Software Foundation, either version 3 of the License, or
|
||
|
(at your option) any later version.
|
||
|
|
||
|
dnSpy is distributed in the hope that it will be useful,
|
||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
GNU General Public License for more details.
|
||
|
|
||
|
You should have received a copy of the GNU General Public License
|
||
|
along with dnSpy. If not, see <http://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
|
||
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Diagnostics;
|
||
|
using System.Diagnostics.CodeAnalysis;
|
||
|
using System.Linq;
|
||
|
using System.Runtime.InteropServices;
|
||
|
using System.Threading;
|
||
|
using dndbg.COM.CorDebug;
|
||
|
using dndbg.COM.MetaData;
|
||
|
using dndbg.DotNet;
|
||
|
using dndbg.Engine;
|
||
|
using dnSpy.Contracts.Debugger;
|
||
|
using dnSpy.Contracts.Debugger.DotNet.Code;
|
||
|
using dnSpy.Contracts.Debugger.DotNet.CorDebug;
|
||
|
using dnSpy.Contracts.Debugger.DotNet.Evaluation;
|
||
|
using dnSpy.Contracts.Debugger.DotNet.Metadata;
|
||
|
using dnSpy.Contracts.Debugger.DotNet.Metadata.Internal;
|
||
|
using dnSpy.Contracts.Debugger.DotNet.Steppers.Engine;
|
||
|
using dnSpy.Contracts.Debugger.Engine;
|
||
|
using dnSpy.Contracts.Debugger.Engine.Steppers;
|
||
|
using dnSpy.Contracts.Debugger.Exceptions;
|
||
|
using dnSpy.Contracts.Metadata;
|
||
|
using dnSpy.Debugger.DotNet.CorDebug.CallStack;
|
||
|
using dnSpy.Debugger.DotNet.CorDebug.Code;
|
||
|
using dnSpy.Debugger.DotNet.CorDebug.DAC;
|
||
|
using dnSpy.Debugger.DotNet.CorDebug.Impl.Attach;
|
||
|
using dnSpy.Debugger.DotNet.CorDebug.Impl.Evaluation;
|
||
|
using dnSpy.Debugger.DotNet.CorDebug.Properties;
|
||
|
using dnSpy.Debugger.DotNet.CorDebug.Steppers;
|
||
|
using dnSpy.Debugger.DotNet.CorDebug.Utilities;
|
||
|
using dnSpy.Debugger.DotNet.Metadata;
|
||
|
|
||
|
namespace dnSpy.Debugger.DotNet.CorDebug.Impl {
|
||
|
abstract partial class DbgEngineImpl : DbgEngine, IClrDacDebugger {
|
||
|
public override DbgStartKind StartKind { get; }
|
||
|
public override string[] DebugTags => new[] { PredefinedDebugTags.DotNetDebugger };
|
||
|
public override event EventHandler<DbgEngineMessage>? Message;
|
||
|
public event EventHandler? ClrDacRunning;
|
||
|
public event EventHandler? ClrDacPaused;
|
||
|
public event EventHandler? ClrDacTerminated;
|
||
|
|
||
|
internal DebuggerThread DebuggerThread => debuggerThread;
|
||
|
internal DbgObjectFactory ObjectFactory => objectFactory;
|
||
|
|
||
|
readonly DebuggerSettings debuggerSettings;
|
||
|
readonly Lazy<DbgDotNetNativeCodeLocationFactory> dbgDotNetNativeCodeLocationFactory;
|
||
|
readonly Lazy<DbgDotNetCodeLocationFactory> dbgDotNetCodeLocationFactory;
|
||
|
readonly DbgEngineStepperFactory dbgEngineStepperFactory;
|
||
|
readonly DebuggerThread debuggerThread;
|
||
|
readonly object lockObj;
|
||
|
readonly ClrDacProvider clrDacProvider;
|
||
|
internal ClrDac clrDac;
|
||
|
bool clrDacInitd;
|
||
|
readonly DbgManager dbgManager;
|
||
|
readonly DbgModuleMemoryRefreshedNotifier2 dbgModuleMemoryRefreshedNotifier;
|
||
|
DnDebugger dnDebugger;
|
||
|
SafeHandle hProcess_debuggee;
|
||
|
DbgObjectFactory objectFactory;
|
||
|
readonly Dictionary<DnAppDomain, DbgEngineAppDomain> toEngineAppDomain;
|
||
|
readonly Dictionary<CorModule, DbgEngineModule> toEngineModule;
|
||
|
readonly Dictionary<DnThread, DbgEngineThread> toEngineThread;
|
||
|
readonly Dictionary<DnAssembly, List<DnModule>> toAssemblyModules;
|
||
|
internal readonly StackFrameData stackFrameData;
|
||
|
readonly HashSet<DnDebuggerObjectHolder> objectHolders;
|
||
|
readonly DmdRuntime dmdRuntime;
|
||
|
readonly Dictionary<CorModule, DmdDynamicModuleHelperImpl> toDynamicModuleHelper;
|
||
|
internal DmdDispatcherImpl DmdDispatcher { get; }
|
||
|
internal DbgRawMetadataService RawMetadataService { get; }
|
||
|
readonly List<DbgDotNetValueImpl> dotNetValuesToCloseOnContinue;
|
||
|
readonly List<DbgCorValueHolder> valuesToCloseNow;
|
||
|
bool isUnhandledException;
|
||
|
bool redirectConsoleOutput;
|
||
|
|
||
|
protected DbgEngineImpl(DbgEngineImplDependencies deps, DbgManager dbgManager, DbgStartKind startKind) {
|
||
|
if (deps is null)
|
||
|
throw new ArgumentNullException(nameof(deps));
|
||
|
dnDebugger = null!;
|
||
|
hProcess_debuggee = null!;
|
||
|
objectFactory = null!;
|
||
|
internalRuntime = null!;
|
||
|
StartKind = startKind;
|
||
|
lockObj = new object();
|
||
|
toEngineAppDomain = new Dictionary<DnAppDomain, DbgEngineAppDomain>();
|
||
|
toEngineModule = new Dictionary<CorModule, DbgEngineModule>();
|
||
|
toEngineThread = new Dictionary<DnThread, DbgEngineThread>();
|
||
|
toAssemblyModules = new Dictionary<DnAssembly, List<DnModule>>();
|
||
|
stackFrameData = new StackFrameData();
|
||
|
objectHolders = new HashSet<DnDebuggerObjectHolder>();
|
||
|
debuggerSettings = deps.DebuggerSettings;
|
||
|
dbgDotNetNativeCodeLocationFactory = deps.DbgDotNetNativeCodeLocationFactory;
|
||
|
dbgDotNetCodeLocationFactory = deps.DbgDotNetCodeLocationFactory;
|
||
|
dbgEngineStepperFactory = deps.EngineStepperFactory;
|
||
|
this.dbgManager = dbgManager ?? throw new ArgumentNullException(nameof(dbgManager));
|
||
|
dbgModuleMemoryRefreshedNotifier = deps.DbgModuleMemoryRefreshedNotifier;
|
||
|
clrDacProvider = deps.ClrDacProvider;
|
||
|
clrDac = NullClrDac.Instance;
|
||
|
debuggerThread = new DebuggerThread("CorDebug");
|
||
|
debuggerThread.CallDispatcherRun();
|
||
|
dotNetValuesToCloseOnContinue = new List<DbgDotNetValueImpl>();
|
||
|
valuesToCloseNow = new List<DbgCorValueHolder>();
|
||
|
currentReturnValues = Array.Empty<DbgDotNetReturnValueInfo>();
|
||
|
dmdRuntime = DmdRuntimeFactory.CreateRuntime(new DmdEvaluatorImpl(this), IntPtr.Size == 4 ? DmdImageFileMachine.I386 : DmdImageFileMachine.AMD64);
|
||
|
toDynamicModuleHelper = new Dictionary<CorModule, DmdDynamicModuleHelperImpl>();
|
||
|
DmdDispatcher = new DmdDispatcherImpl(this);
|
||
|
RawMetadataService = deps.RawMetadataService;
|
||
|
}
|
||
|
|
||
|
internal event EventHandler<ClassLoadedEventArgs>? ClassLoaded;
|
||
|
|
||
|
internal bool CheckCorDebugThread() => debuggerThread.CheckAccess();
|
||
|
internal void VerifyCorDebugThread() => debuggerThread.VerifyAccess();
|
||
|
internal T InvokeCorDebugThread<T>(Func<T> callback) => debuggerThread.Invoke(callback);
|
||
|
internal void CorDebugThread(Action callback) => debuggerThread.BeginInvoke(callback);
|
||
|
internal string DebuggeeVersion => dnDebugger.DebuggeeVersion;
|
||
|
internal bool IsPaused => dnDebugger.ProcessState == DebuggerProcessState.Paused;
|
||
|
|
||
|
internal DbgEngineMessageFlags GetMessageFlags(bool pause = false) {
|
||
|
VerifyCorDebugThread();
|
||
|
var flags = DbgEngineMessageFlags.None;
|
||
|
if (pause)
|
||
|
flags |= DbgEngineMessageFlags.Pause;
|
||
|
if (dnDebugger?.IsEvaluating == true)
|
||
|
flags |= DbgEngineMessageFlags.Continue;
|
||
|
return flags;
|
||
|
}
|
||
|
|
||
|
void DnDebugger_DebugCallbackEvent(DnDebugger dbg, DebugCallbackEventArgs e) {
|
||
|
string? msg;
|
||
|
DbgModule? module;
|
||
|
switch (e.Kind) {
|
||
|
case DebugCallbackKind.CreateProcess:
|
||
|
var cp = (CreateProcessDebugCallbackEventArgs)e;
|
||
|
int pid = cp.CorProcess?.ProcessId ?? -1;
|
||
|
hProcess_debuggee = Native.NativeMethods.OpenProcess(Native.NativeMethods.PROCESS_QUERY_LIMITED_INFORMATION, false, (uint)pid);
|
||
|
SendMessage(new DbgMessageConnected(pid, GetMessageFlags()));
|
||
|
e.AddPauseReason(DebuggerPauseReason.Other);
|
||
|
break;
|
||
|
|
||
|
case DebugCallbackKind.CreateAppDomain:
|
||
|
// CreateProcess is too early, we must do this when the AppDomain gets created
|
||
|
if (!clrDacInitd) {
|
||
|
clrDacInitd = true;
|
||
|
var p = dnDebugger.Processes.FirstOrDefault();
|
||
|
if (p is not null)
|
||
|
clrDac = clrDacProvider.Create(p.ProcessId, dnDebugger.CLRPath, this);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case DebugCallbackKind.Exception2:
|
||
|
var e2 = (Exception2DebugCallbackEventArgs)e;
|
||
|
DbgExceptionEventFlags exFlags;
|
||
|
if (e2.EventType == CorDebugExceptionCallbackType.DEBUG_EXCEPTION_FIRST_CHANCE)
|
||
|
exFlags = DbgExceptionEventFlags.FirstChance;
|
||
|
else if (e2.EventType == CorDebugExceptionCallbackType.DEBUG_EXCEPTION_UNHANDLED) {
|
||
|
exFlags = DbgExceptionEventFlags.SecondChance | DbgExceptionEventFlags.Unhandled;
|
||
|
isUnhandledException = true;
|
||
|
}
|
||
|
else
|
||
|
break;
|
||
|
|
||
|
// Ignore exceptions when evaluating except if it's an unhandled exception, those must always be reported
|
||
|
if (dbg.IsEvaluating && e2.EventType != CorDebugExceptionCallbackType.DEBUG_EXCEPTION_UNHANDLED)
|
||
|
break;
|
||
|
|
||
|
module = TryGetModule(e2.CorFrame, e2.CorThread);
|
||
|
var exObj = e2.CorThread?.CurrentException;
|
||
|
var reflectionAppDomain = module?.GetReflectionModule()?.AppDomain;
|
||
|
DbgDotNetValueImpl? dnExObj = null;
|
||
|
try {
|
||
|
if (exObj is not null && reflectionAppDomain is not null)
|
||
|
dnExObj = CreateDotNetValue_CorDebug(exObj, reflectionAppDomain, tryCreateStrongHandle: false) as DbgDotNetValueImpl;
|
||
|
objectFactory.CreateException(new DbgExceptionId(PredefinedExceptionCategories.DotNet, TryGetExceptionName(dnExObj) ?? "???"), exFlags, TryGetExceptionMessage(dnExObj), TryGetThread(e2.CorThread), module, GetMessageFlags());
|
||
|
e.AddPauseReason(DebuggerPauseReason.Other);
|
||
|
}
|
||
|
finally {
|
||
|
dnExObj?.Dispose();
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case DebugCallbackKind.MDANotification:
|
||
|
if (dbg.IsEvaluating)
|
||
|
break;
|
||
|
var mdan = (MDANotificationDebugCallbackEventArgs)e;
|
||
|
objectFactory.CreateException(new DbgExceptionId(PredefinedExceptionCategories.MDA, mdan.CorMDA?.Name ?? "???"), DbgExceptionEventFlags.FirstChance, mdan.CorMDA?.Description, TryGetThread(mdan.CorThread), TryGetModule(null, mdan.CorThread), GetMessageFlags());
|
||
|
e.AddPauseReason(DebuggerPauseReason.Other);
|
||
|
break;
|
||
|
|
||
|
case DebugCallbackKind.LogMessage:
|
||
|
if (dbg.IsEvaluating)
|
||
|
break;
|
||
|
var lmsgArgs = (LogMessageDebugCallbackEventArgs)e;
|
||
|
msg = lmsgArgs.Message;
|
||
|
if (msg is not null) {
|
||
|
e.AddPauseReason(DebuggerPauseReason.Other);
|
||
|
var thread = TryGetThread(lmsgArgs.CorThread);
|
||
|
SendMessage(new DbgMessageProgramMessage(msg, thread, GetMessageFlags()));
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case DebugCallbackKind.LoadClass:
|
||
|
var lcArgs = (LoadClassDebugCallbackEventArgs)e;
|
||
|
var cls = lcArgs.CorClass;
|
||
|
Debug2.Assert(cls is not null);
|
||
|
if (cls is not null) {
|
||
|
var dnModule = dbg.TryGetModule(lcArgs.CorAppDomain, cls);
|
||
|
if (dnModule!.IsDynamic == true) {
|
||
|
UpdateDynamicModuleIds(dnModule);
|
||
|
module = TryGetModule(dnModule.CorModule);
|
||
|
Debug2.Assert(module is not null);
|
||
|
if (module is not null)
|
||
|
dbgModuleMemoryRefreshedNotifier.RaiseModulesRefreshed(new[] { module });
|
||
|
if (dnModule.CorModuleDef is not null && module is not null) {
|
||
|
if (TryGetModuleData(module, out var data))
|
||
|
data.OnLoadClass();
|
||
|
ClassLoaded?.Invoke(this, new ClassLoadedEventArgs(module, cls.Token));
|
||
|
}
|
||
|
GetDynamicModuleHelper(dnModule).RaiseTypeLoaded(new DmdTypeLoadedEventArgs((int)cls.Token));
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case DebugCallbackKind.DebuggerError:
|
||
|
var deArgs = (DebuggerErrorDebugCallbackEventArgs)e;
|
||
|
if (deArgs.HError == CordbgErrors.CORDBG_E_UNCOMPATIBLE_PLATFORMS)
|
||
|
msg = GetIncompatiblePlatformErrorMessage();
|
||
|
else
|
||
|
msg = string.Format(dnSpy_Debugger_DotNet_CorDebug_Resources.Error_CLRDebuggerErrorOccurred, deArgs.HError, deArgs.ErrorCode);
|
||
|
SendMessage(new DbgMessageBreak(msg, GetMessageFlags(pause: true)));
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal void RaiseModulesRefreshed(DbgModule module) => dbgModuleMemoryRefreshedNotifier.RaiseModulesRefreshed(new[] { module });
|
||
|
|
||
|
internal DmdDynamicModuleHelperImpl GetDynamicModuleHelper(DnModule dnModule) {
|
||
|
Debug.Assert(dnModule.IsDynamic);
|
||
|
lock (lockObj) {
|
||
|
if (!toDynamicModuleHelper.TryGetValue(dnModule.CorModule, out var helper))
|
||
|
toDynamicModuleHelper.Add(dnModule.CorModule, helper = new DmdDynamicModuleHelperImpl(this));
|
||
|
return helper;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
string? TryGetExceptionName(DbgDotNetValue? exObj) {
|
||
|
if (exObj is null)
|
||
|
return null;
|
||
|
var type = exObj.Type;
|
||
|
if (type.IsConstructedGenericType)
|
||
|
type = type.GetGenericTypeDefinition();
|
||
|
return type.FullName;
|
||
|
}
|
||
|
|
||
|
string? TryGetExceptionMessage(DbgDotNetValueImpl? exObj) {
|
||
|
if (exObj is null)
|
||
|
return null;
|
||
|
var res = ReadField_CorDebug(exObj, KnownMemberNames.Exception_Message_FieldName, null);
|
||
|
if (res is null || !res.Value.HasRawValue)
|
||
|
return null;
|
||
|
return res.Value.RawValue as string ?? dnSpy_Debugger_DotNet_CorDebug_Resources.ExceptionMessageIsNull;
|
||
|
}
|
||
|
|
||
|
internal DbgThread? TryGetThread(CorThread? thread) {
|
||
|
if (thread is null)
|
||
|
return null;
|
||
|
var dnThread = dnDebugger.Processes.FirstOrDefault()?.Threads.FirstOrDefault(a => a.CorThread.Equals(thread));
|
||
|
return TryGetThread(dnThread);
|
||
|
}
|
||
|
|
||
|
DbgThread? TryGetThread(DnThread? dnThread) {
|
||
|
if (dnThread is null)
|
||
|
return null;
|
||
|
DbgEngineThread? engineThread;
|
||
|
lock (lockObj)
|
||
|
toEngineThread.TryGetValue(dnThread, out engineThread);
|
||
|
return engineThread?.Thread;
|
||
|
}
|
||
|
|
||
|
DbgModule? TryGetModule(CorFrame? frame, CorThread? thread) {
|
||
|
if (frame?.Function is null && thread is not null) {
|
||
|
frame = thread.ActiveFrame;
|
||
|
if (frame?.Function is null) {
|
||
|
// Ignore the first frame(s) that have a null function. This rarely happens (eg. it
|
||
|
// happens when debugging dnSpy built for .NET x86)
|
||
|
frame = thread.AllFrames.FirstOrDefault(a => a.Function is not null);
|
||
|
}
|
||
|
}
|
||
|
return TryGetModule(frame?.Function?.Module);
|
||
|
}
|
||
|
|
||
|
internal DbgModule? TryGetModule(CorModule? corModule) {
|
||
|
if (corModule is null)
|
||
|
return null;
|
||
|
lock (lockObj) {
|
||
|
if (toEngineModule.TryGetValue(corModule, out var engineModule))
|
||
|
return engineModule.Module;
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
void HookDnDebuggerEvents() {
|
||
|
dnDebugger.DebugCallbackEvent += DnDebugger_DebugCallbackEvent;
|
||
|
dnDebugger.OnProcessStateChanged += DnDebugger_OnProcessStateChanged;
|
||
|
dnDebugger.OnNameChanged += DnDebugger_OnNameChanged;
|
||
|
dnDebugger.OnThreadAdded += DnDebugger_OnThreadAdded;
|
||
|
dnDebugger.OnAppDomainAdded += DnDebugger_OnAppDomainAdded;
|
||
|
dnDebugger.OnModuleAdded += DnDebugger_OnModuleAdded;
|
||
|
dnDebugger.OnCorModuleDefCreated += DnDebugger_OnCorModuleDefCreated;
|
||
|
dnDebugger.OnAttachComplete += DnDebugger_OnAttachComplete;
|
||
|
}
|
||
|
|
||
|
void UnhookDnDebuggerEventsAndCloseProcessHandle() {
|
||
|
if (dnDebugger is not null) {
|
||
|
dnDebugger.DebugCallbackEvent -= DnDebugger_DebugCallbackEvent;
|
||
|
dnDebugger.OnProcessStateChanged -= DnDebugger_OnProcessStateChanged;
|
||
|
dnDebugger.OnNameChanged -= DnDebugger_OnNameChanged;
|
||
|
dnDebugger.OnThreadAdded -= DnDebugger_OnThreadAdded;
|
||
|
dnDebugger.OnAppDomainAdded -= DnDebugger_OnAppDomainAdded;
|
||
|
dnDebugger.OnModuleAdded -= DnDebugger_OnModuleAdded;
|
||
|
dnDebugger.OnCorModuleDefCreated -= DnDebugger_OnCorModuleDefCreated;
|
||
|
dnDebugger.OnAttachComplete -= DnDebugger_OnAttachComplete;
|
||
|
dnDebugger.OnRedirectedOutput -= DnDebugger_OnRedirectedOutput;
|
||
|
}
|
||
|
hProcess_debuggee?.Close();
|
||
|
}
|
||
|
|
||
|
void DnDebugger_OnAttachComplete(object? sender, EventArgs e) => DetectMainThread();
|
||
|
|
||
|
void DnDebugger_OnProcessStateChanged(object? sender, DebuggerEventArgs e) {
|
||
|
Debug2.Assert(sender is not null && sender == dnDebugger);
|
||
|
|
||
|
if (dnDebugger.ProcessState == DebuggerProcessState.Terminated) {
|
||
|
if (hProcess_debuggee is null || hProcess_debuggee.IsClosed || hProcess_debuggee.IsInvalid || !Native.NativeMethods.GetExitCodeProcess(hProcess_debuggee.DangerousGetHandle(), out int exitCode))
|
||
|
exitCode = -1;
|
||
|
clrDac = NullClrDac.Instance;
|
||
|
ClrDacTerminated?.Invoke(this, EventArgs.Empty);
|
||
|
UnhookDnDebuggerEventsAndCloseProcessHandle();
|
||
|
|
||
|
SendMessage(new DbgMessageDisconnected(exitCode, GetMessageFlags()));
|
||
|
return;
|
||
|
}
|
||
|
else if (dnDebugger.ProcessState == DebuggerProcessState.Paused) {
|
||
|
ClrDacPaused?.Invoke(this, EventArgs.Empty);
|
||
|
UpdateThreadProperties_CorDebug();
|
||
|
|
||
|
foreach (var debuggerState in dnDebugger.DebuggerStates) {
|
||
|
foreach (var pauseState in debuggerState.PauseStates) {
|
||
|
if (pauseState.Handled)
|
||
|
continue;
|
||
|
pauseState.Handled = true;
|
||
|
switch (pauseState.Reason) {
|
||
|
case DebuggerPauseReason.Other:
|
||
|
// We use this reason when we pause the process, DbgManager already knows that we're paused
|
||
|
continue;
|
||
|
|
||
|
case DebuggerPauseReason.AsyncStepperBreakpoint:
|
||
|
// Used by async stepper code. We shouldn't notify DbgManager. The async stepper code will eventually
|
||
|
// create a stepper event.
|
||
|
continue;
|
||
|
|
||
|
case DebuggerPauseReason.UserBreak:
|
||
|
// BreakCore() sends the Break message
|
||
|
continue;
|
||
|
|
||
|
case DebuggerPauseReason.ILCodeBreakpoint:
|
||
|
var ilbp = (ILCodeBreakpointPauseState)pauseState;
|
||
|
SendCodeBreakpointHitMessage_CorDebug(ilbp.Breakpoint, TryGetThread(ilbp.CorThread));
|
||
|
break;
|
||
|
|
||
|
case DebuggerPauseReason.Break:
|
||
|
var bs = (BreakPauseState)pauseState;
|
||
|
SendMessage(new DbgMessageProgramBreak(TryGetThread(bs.CorThread), GetMessageFlags()));
|
||
|
break;
|
||
|
|
||
|
case DebuggerPauseReason.NativeCodeBreakpoint:
|
||
|
var nbp = (NativeCodeBreakpointPauseState)pauseState;
|
||
|
SendCodeBreakpointHitMessage_CorDebug(nbp.Breakpoint, TryGetThread(nbp.CorThread));
|
||
|
break;
|
||
|
|
||
|
case DebuggerPauseReason.EntryPointBreakpoint:
|
||
|
var epbp = (EntryPointBreakpointPauseState)pauseState;
|
||
|
SendMessage(new DbgMessageEntryPointBreak(TryGetThread(epbp.CorThread), GetMessageFlags()));
|
||
|
break;
|
||
|
|
||
|
case DebuggerPauseReason.Eval:
|
||
|
// Don't send a message, that will confuse DbgManager. It thinks we're running or paused.
|
||
|
break;
|
||
|
|
||
|
case DebuggerPauseReason.DebugEventBreakpoint:
|
||
|
case DebuggerPauseReason.AnyDebugEventBreakpoint:
|
||
|
SendMessage(new DbgMessageBreak(TryGetThread(debuggerState.Thread), GetMessageFlags()));
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
Debug.Fail($"Unknown reason: {pauseState.Reason}");
|
||
|
SendMessage(new DbgMessageBreak(TryGetThread(debuggerState.Thread), GetMessageFlags()));
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void DnDebugger_OnRedirectedOutput(object? sender, RedirectedOutputEventArgs e) {
|
||
|
var source = e.IsStandardOutput ? AsyncProgramMessageSource.StandardOutput : AsyncProgramMessageSource.StandardError;
|
||
|
SendMessage(new DbgMessageAsyncProgramMessage(source, e.Text));
|
||
|
}
|
||
|
|
||
|
void DnDebugger_OnNameChanged(object? sender, NameChangedDebuggerEventArgs e) {
|
||
|
TryGetEngineAppDomain(e.AppDomain)?.UpdateName(e.AppDomain?.Name);
|
||
|
OnNewThreadName_CorDebug(e.Thread);
|
||
|
}
|
||
|
|
||
|
DbgEngineAppDomain? TryGetEngineAppDomain(DnAppDomain? dnAppDomain) {
|
||
|
if (dnAppDomain is null)
|
||
|
return null;
|
||
|
DbgEngineAppDomain? engineAppDomain;
|
||
|
bool b;
|
||
|
lock (lockObj)
|
||
|
b = toEngineAppDomain.TryGetValue(dnAppDomain, out engineAppDomain);
|
||
|
Debug.Assert(b);
|
||
|
return engineAppDomain;
|
||
|
}
|
||
|
|
||
|
void DnDebugger_OnAppDomainAdded(object? sender, AppDomainDebuggerEventArgs e) {
|
||
|
Debug2.Assert(objectFactory is not null);
|
||
|
if (e.Added) {
|
||
|
e.ShouldPause = true;
|
||
|
var appDomain = dmdRuntime.CreateAppDomain(e.AppDomain.Id);
|
||
|
var internalAppDomain = new DbgCorDebugInternalAppDomainImpl(appDomain, e.AppDomain);
|
||
|
var engineAppDomain = objectFactory.CreateAppDomain<object>(internalAppDomain, e.AppDomain.Name, e.AppDomain.Id, GetMessageFlags(), data: null, onCreated: engineAppDomain2 => internalAppDomain.SetAppDomain(engineAppDomain2.AppDomain));
|
||
|
lock (lockObj)
|
||
|
toEngineAppDomain.Add(e.AppDomain, engineAppDomain);
|
||
|
}
|
||
|
else {
|
||
|
DbgEngineAppDomain? engineAppDomain;
|
||
|
lock (lockObj) {
|
||
|
if (toEngineAppDomain.TryGetValue(e.AppDomain, out engineAppDomain)) {
|
||
|
toEngineAppDomain.Remove(e.AppDomain);
|
||
|
var appDomain = engineAppDomain.AppDomain;
|
||
|
dmdRuntime.Remove(((DbgCorDebugInternalAppDomainImpl)appDomain.InternalAppDomain).ReflectionAppDomain);
|
||
|
foreach (var kv in toEngineThread.ToArray()) {
|
||
|
if (kv.Value.Thread.AppDomain == appDomain)
|
||
|
toEngineThread.Remove(kv.Key);
|
||
|
}
|
||
|
foreach (var kv in toEngineModule.ToArray()) {
|
||
|
if (kv.Value.Module.AppDomain == appDomain) {
|
||
|
toEngineModule.Remove(kv.Key);
|
||
|
kv.Value.Remove(GetMessageFlags());
|
||
|
toDynamicModuleHelper.Remove(kv.Key);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (engineAppDomain is not null) {
|
||
|
e.ShouldPause = true;
|
||
|
engineAppDomain.Remove(GetMessageFlags());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
sealed class DbgModuleData {
|
||
|
public DbgEngineImpl Engine { get; }
|
||
|
public DnModule DnModule { get; }
|
||
|
public ModuleId ModuleId { get; private set; }
|
||
|
public bool HasUpdatedModuleId { get; private set; }
|
||
|
public int LoadClassVersion => loadClassVersion;
|
||
|
volatile int loadClassVersion;
|
||
|
public DbgModuleData(DbgEngineImpl engine, DnModule dnModule, ModuleId moduleId) {
|
||
|
Engine = engine;
|
||
|
DnModule = dnModule;
|
||
|
ModuleId = moduleId;
|
||
|
}
|
||
|
public void OnLoadClass() => Interlocked.Increment(ref loadClassVersion);
|
||
|
public void UpdateModuleId(ModuleId moduleId) {
|
||
|
if (!moduleId.IsDynamic)
|
||
|
throw new InvalidOperationException();
|
||
|
ModuleId = moduleId;
|
||
|
HasUpdatedModuleId = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal ModuleId GetModuleId(DbgModule module) {
|
||
|
if (TryGetModuleData(module, out var data))
|
||
|
return data.ModuleId;
|
||
|
throw new InvalidOperationException();
|
||
|
}
|
||
|
|
||
|
bool TryGetModuleData(DbgModule module, [NotNullWhen(true)] out DbgModuleData? data) {
|
||
|
if (module.TryGetData(out data) && data.Engine == this)
|
||
|
return true;
|
||
|
data = null;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
internal bool TryGetDnModuleAndVersion(DbgModule module, [NotNullWhen(true)] out DnModule? dnModule, out int loadClassVersion) {
|
||
|
if (module.TryGetData(out DbgModuleData? data) && data.Engine == this) {
|
||
|
dnModule = data.DnModule;
|
||
|
loadClassVersion = data.LoadClassVersion;
|
||
|
return true;
|
||
|
}
|
||
|
dnModule = null;
|
||
|
loadClassVersion = -1;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
internal bool TryGetDnModule(DbgModule module, [NotNullWhen(true)] out DnModule? dnModule) {
|
||
|
if (module.TryGetData(out DbgModuleData? data) && data.Engine == this) {
|
||
|
dnModule = data.DnModule;
|
||
|
return true;
|
||
|
}
|
||
|
dnModule = null;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// When a dynamic assembly is created with option Run, a module gets created and its
|
||
|
// metadata name is "RefEmit_InMemoryManifestModule". Shortly thereafter, its name
|
||
|
// gets changed to the name the user chose.
|
||
|
// This name is also saved in ModuleIds, and used when setting breakpoints...
|
||
|
// There's code that caches ModuleIds, but they don't cache it if IsDynamic is true.
|
||
|
// This method updates the ModuleId and resets breakpoints in the module.
|
||
|
void UpdateDynamicModuleIds(DnModule dnModule) {
|
||
|
debuggerThread.VerifyAccess();
|
||
|
if (!dnModule.IsDynamic)
|
||
|
return;
|
||
|
var module = TryGetModule(dnModule.CorModule);
|
||
|
if (module is null || !TryGetModuleData(module, out var data) || data.HasUpdatedModuleId)
|
||
|
return;
|
||
|
List<(DbgModule dbgModule, DnModule dnModule)>? updatedModules = null;
|
||
|
lock (lockObj) {
|
||
|
if (toAssemblyModules.TryGetValue(dnModule.Assembly, out var modules)) {
|
||
|
for (int i = 0; i < modules.Count; i++) {
|
||
|
dnModule = modules[i];
|
||
|
if (!dnModule.IsDynamic)
|
||
|
continue;
|
||
|
if (!toEngineModule.TryGetValue(dnModule.CorModule, out var em))
|
||
|
continue;
|
||
|
if (!TryGetModuleData(em.Module, out data))
|
||
|
continue;
|
||
|
dnModule.CorModule.ClearCachedDnlibName();
|
||
|
var moduleId = dnModule.DnModuleId.ToModuleId();
|
||
|
if (data.ModuleId == moduleId)
|
||
|
continue;
|
||
|
data.UpdateModuleId(moduleId);
|
||
|
if (dnModule.CorModuleDef is not null) {
|
||
|
//TODO: This doesn't update the treeview node
|
||
|
dnModule.CorModuleDef.Name = moduleId.ModuleName;
|
||
|
}
|
||
|
if (updatedModules is null)
|
||
|
updatedModules = new List<(DbgModule, DnModule)>();
|
||
|
updatedModules.Add((em.Module, dnModule));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (updatedModules is not null) {
|
||
|
foreach (var info in updatedModules) {
|
||
|
var mdi = info.dnModule.CorModule.GetMetaDataInterface<IMetaDataImport2>();
|
||
|
var scopeName = MDAPI.GetModuleName(mdi) ?? string.Empty;
|
||
|
((DbgCorDebugInternalModuleImpl)info.dbgModule.InternalModule).ReflectionModule!.ScopeName = scopeName;
|
||
|
}
|
||
|
dbgModuleMemoryRefreshedNotifier.RaiseModulesRefreshed(updatedModules.Select(a => a.dbgModule).ToArray());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void DnDebugger_OnModuleAdded(object? sender, ModuleDebuggerEventArgs e) {
|
||
|
Debug2.Assert(objectFactory is not null);
|
||
|
if (e.Added) {
|
||
|
e.ShouldPause = true;
|
||
|
var appDomain = TryGetEngineAppDomain(e.Module.AppDomain)?.AppDomain;
|
||
|
var moduleId = e.Module.DnModuleId.ToModuleId();
|
||
|
var moduleData = new DbgModuleData(this, e.Module, moduleId);
|
||
|
var engineModule = ModuleCreator.CreateModule(this, objectFactory, appDomain, e.Module, moduleData);
|
||
|
lock (lockObj) {
|
||
|
if (!toAssemblyModules.TryGetValue(e.Module.Assembly, out var modules))
|
||
|
toAssemblyModules.Add(e.Module.Assembly, modules = new List<DnModule>());
|
||
|
modules.Add(e.Module);
|
||
|
toEngineModule.Add(e.Module.CorModule, engineModule);
|
||
|
}
|
||
|
|
||
|
var reflectionModule = ((DbgCorDebugInternalModuleImpl)engineModule.Module.InternalModule).ReflectionModule!;
|
||
|
if (reflectionModule.IsCorLib) {
|
||
|
var type = reflectionModule.AppDomain.GetWellKnownType(DmdWellKnownType.System_Diagnostics_Debugger_CrossThreadDependencyNotification, isOptional: true);
|
||
|
Debug2.Assert(type is not null || dnDebugger.DebuggeeVersion.StartsWith("v2."));
|
||
|
if (type is not null)
|
||
|
dnDebugger.AddCustomNotificationClassToken(e.Module, (uint)type.MetadataToken);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
DbgEngineModule? engineModule;
|
||
|
lock (lockObj) {
|
||
|
if (toAssemblyModules.TryGetValue(e.Module.Assembly, out var modules)) {
|
||
|
modules.Remove(e.Module);
|
||
|
if (modules.Count == 0)
|
||
|
toAssemblyModules.Remove(e.Module.Assembly);
|
||
|
}
|
||
|
toDynamicModuleHelper.Remove(e.Module.CorModule);
|
||
|
if (toEngineModule.TryGetValue(e.Module.CorModule, out engineModule)) {
|
||
|
toEngineModule.Remove(e.Module.CorModule);
|
||
|
((DbgCorDebugInternalModuleImpl)engineModule.Module.InternalModule).Remove();
|
||
|
}
|
||
|
}
|
||
|
if (engineModule is not null) {
|
||
|
e.ShouldPause = true;
|
||
|
engineModule.Remove(GetMessageFlags());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal (CorModuleDef? metadata, ModuleId moduleId) GetDynamicMetadata_EngineThread(DbgModule module) {
|
||
|
debuggerThread.VerifyAccess();
|
||
|
if (module is null)
|
||
|
throw new ArgumentNullException(nameof(module));
|
||
|
if (!TryGetModuleData(module, out var data))
|
||
|
return (null, default);
|
||
|
return (data.DnModule.GetOrCreateCorModuleDef(), data.DnModule.DnModuleId.ToModuleId());
|
||
|
}
|
||
|
|
||
|
internal static ModuleId? TryGetModuleId(DbgModule module) {
|
||
|
if (module.TryGetData(out DbgModuleData? data))
|
||
|
return data.ModuleId;
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
void SendMessage(DbgEngineMessage message) => Message?.Invoke(this, message);
|
||
|
|
||
|
public override void Start(DebugProgramOptions options) => CorDebugThread(() => {
|
||
|
if (StartKind == DbgStartKind.Start)
|
||
|
StartCore((CorDebugStartDebuggingOptions)options);
|
||
|
else if (StartKind == DbgStartKind.Attach)
|
||
|
AttachCore((CorDebugAttachToProgramOptions)options);
|
||
|
else
|
||
|
throw new InvalidOperationException();
|
||
|
});
|
||
|
|
||
|
protected abstract CLRTypeDebugInfo CreateDebugInfo(CorDebugStartDebuggingOptions options);
|
||
|
protected abstract CLRTypeAttachInfo CreateAttachInfo(CorDebugAttachToProgramOptions options);
|
||
|
|
||
|
void StartCore(CorDebugStartDebuggingOptions options) {
|
||
|
debuggerThread.VerifyAccess();
|
||
|
try {
|
||
|
if (debuggerThread.HasShutdownStarted)
|
||
|
throw new InvalidOperationException("Dispatcher has shut down");
|
||
|
|
||
|
var env = new DbgEnvironment(options.Environment);
|
||
|
bool disableMDA = !debuggerSettings.EnableManagedDebuggingAssistants;
|
||
|
// If 32-bit .NET Framework and MDAs are enabled, pinvoke methods are called from clr.dll
|
||
|
// which breaks anti-IsDebuggerPresent() code. Our workaround is to disable MDAs.
|
||
|
// We can only debug processes with the same bitness, so check IntPtr.Size.
|
||
|
if (IntPtr.Size == 4 && debuggerSettings.AntiIsDebuggerPresent && options is DotNetFrameworkStartDebuggingOptions)
|
||
|
disableMDA = true;
|
||
|
// .NET doesn't support MDAs
|
||
|
if (options is DotNetStartDebuggingOptions)
|
||
|
disableMDA = false;
|
||
|
if (disableMDA) {
|
||
|
// https://docs.microsoft.com/en-us/dotnet/framework/debug-trace-profile/diagnosing-errors-with-managed-debugging-assistants
|
||
|
env.Add("COMPlus_MDA", "0");
|
||
|
}
|
||
|
if (debuggerSettings.SuppressJITOptimization_SystemModules) {
|
||
|
if (options is DotNetFrameworkStartDebuggingOptions)
|
||
|
env.Add("COMPlus_ZapDisable", "1");
|
||
|
else if (options is DotNetStartDebuggingOptions)
|
||
|
env.Add("COMPlus_ReadyToRun", "0");
|
||
|
else
|
||
|
Debug.Fail("Unreachable code");
|
||
|
}
|
||
|
// Disable OS heap debugging https://github.com/dnSpy/dnSpy/issues/1106
|
||
|
env.Add("_NO_DEBUG_HEAP", "1");
|
||
|
|
||
|
var dbgOptions = new DebugProcessOptions(CreateDebugInfo(options)) {
|
||
|
DebugMessageDispatcher = debuggerThread.GetDebugMessageDispatcher(),
|
||
|
CurrentDirectory = options.WorkingDirectory,
|
||
|
Filename = PathUtils.NormalizeFilename(options.Filename),
|
||
|
CommandLine = options.CommandLine,
|
||
|
BreakProcessKind = GetBreakProcessKind(options.BreakKind),
|
||
|
Environment = env.Environment,
|
||
|
};
|
||
|
Debug2.Assert(dbgOptions.Filename is not null);
|
||
|
dbgOptions.DebugOptions.IgnoreBreakInstructions = false;
|
||
|
dbgOptions.DebugOptions.DebugOptionsProvider = new DebugOptionsProviderImpl(debuggerSettings);
|
||
|
if (debuggerSettings.RedirectGuiConsoleOutput && PortableExecutableFileHelpers.IsGuiApp(options.Filename))
|
||
|
dbgOptions.RedirectConsoleOutput = true;
|
||
|
|
||
|
redirectConsoleOutput = dbgOptions.RedirectConsoleOutput;
|
||
|
dnDebugger = DnDebugger.DebugProcess(dbgOptions);
|
||
|
OnDebugProcess(dnDebugger);
|
||
|
HookDnDebuggerEvents();
|
||
|
return;
|
||
|
}
|
||
|
catch (Exception ex) {
|
||
|
var cex = ex as COMException;
|
||
|
const int ERROR_NOT_SUPPORTED = unchecked((int)0x80070032);
|
||
|
string errMsg;
|
||
|
if (ex is StartDebuggerException sde) {
|
||
|
switch (sde.Error) {
|
||
|
case StartDebuggerError.UnsupportedBitness:
|
||
|
errMsg = string.Format(dnSpy_Debugger_DotNet_CorDebug_Resources.Error_CouldNotStartDebugger, GetIncompatiblePlatformErrorMessage());
|
||
|
break;
|
||
|
default:
|
||
|
throw new InvalidOperationException();
|
||
|
}
|
||
|
}
|
||
|
else if (cex is not null && cex.ErrorCode == ERROR_NOT_SUPPORTED)
|
||
|
errMsg = string.Format(dnSpy_Debugger_DotNet_CorDebug_Resources.Error_CouldNotStartDebugger, GetIncompatiblePlatformErrorMessage());
|
||
|
else if (cex is not null && cex.ErrorCode == CordbgErrors.CORDBG_E_UNCOMPATIBLE_PLATFORMS)
|
||
|
errMsg = string.Format(dnSpy_Debugger_DotNet_CorDebug_Resources.Error_CouldNotStartDebugger, GetIncompatiblePlatformErrorMessage());
|
||
|
else if (cex is not null && cex.ErrorCode == unchecked((int)0x800702E4)) {
|
||
|
// The x64 CLR debugger doesn't return the correct error code when we try to debug a 32-bit req-admin program.
|
||
|
// It doesn't support debugging 32-bit programs, so it should return CORDBG_E_UNCOMPATIBLE_PLATFORMS or ERROR_NOT_SUPPORTED.
|
||
|
if (IntPtr.Size == 8 && DotNetAssemblyUtilities.TryGetProgramBitness(options.Filename) == 32)
|
||
|
errMsg = string.Format(dnSpy_Debugger_DotNet_CorDebug_Resources.Error_CouldNotStartDebugger, GetIncompatiblePlatformErrorMessage());
|
||
|
else
|
||
|
errMsg = dnSpy_Debugger_DotNet_CorDebug_Resources.Error_CouldNotStartDebuggerRequireAdminPrivLvl;
|
||
|
}
|
||
|
else
|
||
|
errMsg = string.Format(dnSpy_Debugger_DotNet_CorDebug_Resources.Error_CouldNotStartDebuggerCheckAccessToFile, options.Filename ?? "<???>", ex.Message);
|
||
|
|
||
|
SendMessage(new DbgMessageConnected(errMsg, GetMessageFlags()));
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static BreakProcessKind GetBreakProcessKind(string? breakKind) {
|
||
|
if (breakKind == PredefinedBreakKinds.EntryPoint)
|
||
|
return BreakProcessKind.EntryPoint;
|
||
|
return BreakProcessKind.None;
|
||
|
}
|
||
|
|
||
|
static string GetIncompatiblePlatformErrorMessage() {
|
||
|
if (IntPtr.Size == 4)
|
||
|
return dnSpy_Debugger_DotNet_CorDebug_Resources.UseDnSpyExeToDebug64;
|
||
|
return dnSpy_Debugger_DotNet_CorDebug_Resources.UseDnSpy64ExeToDebug32;
|
||
|
}
|
||
|
|
||
|
void AttachCore(CorDebugAttachToProgramOptions options) {
|
||
|
debuggerThread.VerifyAccess();
|
||
|
try {
|
||
|
if (debuggerThread.HasShutdownStarted)
|
||
|
throw new InvalidOperationException("Dispatcher has shut down");
|
||
|
var dbgOptions = new AttachProcessOptions(CreateAttachInfo(options)) {
|
||
|
DebugMessageDispatcher = debuggerThread.GetDebugMessageDispatcher(),
|
||
|
ProcessId = (int)options.ProcessId,
|
||
|
};
|
||
|
dbgOptions.DebugOptions.DebugOptionsProvider = new DebugOptionsProviderImpl(debuggerSettings);
|
||
|
|
||
|
dnDebugger = DnDebugger.Attach(dbgOptions);
|
||
|
if (dnDebugger.Processes.Length == 0)
|
||
|
throw new ErrorException(string.Format(dnSpy_Debugger_DotNet_CorDebug_Resources.Error_CouldNotAttachToProcess, $"PID={options.ProcessId.ToString()}"));
|
||
|
OnDebugProcess(dnDebugger);
|
||
|
HookDnDebuggerEvents();
|
||
|
return;
|
||
|
}
|
||
|
catch (Exception ex) {
|
||
|
string errMsg;
|
||
|
if (ex is ErrorException errEx)
|
||
|
errMsg = errEx.Message;
|
||
|
else if (CorDebugRuntimeKind == CorDebugRuntimeKind.DotNet && ex is ArgumentException) {
|
||
|
// .NET throws ArgumentException if it can't attach to it (.NET Framework throws a COM exception with the correct error message)
|
||
|
errMsg = string.Format(dnSpy_Debugger_DotNet_CorDebug_Resources.Error_CouldNotStartDebugger2,
|
||
|
string.Format(dnSpy_Debugger_DotNet_CorDebug_Resources.Error_ProcessIsAlreadyBeingDebugged, options.ProcessId.ToString()));
|
||
|
}
|
||
|
else
|
||
|
errMsg = string.Format(dnSpy_Debugger_DotNet_CorDebug_Resources.Error_CouldNotStartDebugger2, ex.Message);
|
||
|
|
||
|
SendMessage(new DbgMessageConnected(errMsg, GetMessageFlags()));
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
sealed class ErrorException : Exception {
|
||
|
public ErrorException(string msg) : base(msg) { }
|
||
|
}
|
||
|
|
||
|
protected abstract void OnDebugProcess(DnDebugger dnDebugger);
|
||
|
|
||
|
protected abstract CorDebugRuntimeKind CorDebugRuntimeKind { get; }
|
||
|
|
||
|
sealed class RuntimeData {
|
||
|
public DbgEngineImpl Engine { get; }
|
||
|
public RuntimeData(DbgEngineImpl engine) => Engine = engine;
|
||
|
}
|
||
|
|
||
|
internal static DbgEngineImpl? TryGetEngine(DbgRuntime runtime) {
|
||
|
if (runtime.TryGetData(out RuntimeData? data))
|
||
|
return data.Engine;
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
internal DbgModule[] GetAssemblyModules(DbgModule module) {
|
||
|
if (!TryGetModuleData(module, out var data))
|
||
|
return Array.Empty<DbgModule>();
|
||
|
lock (lockObj) {
|
||
|
toAssemblyModules.TryGetValue(data.DnModule.Assembly, out var modules);
|
||
|
if (modules is null || modules.Count == 0)
|
||
|
return Array.Empty<DbgModule>();
|
||
|
var res = new List<DbgModule>(modules.Count);
|
||
|
foreach (var dnModule in modules) {
|
||
|
if (toEngineModule.TryGetValue(dnModule.CorModule, out var engineModule))
|
||
|
res.Add(engineModule.Module);
|
||
|
}
|
||
|
return res.ToArray();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal IDbgDotNetRuntime DotNetRuntime => internalRuntime;
|
||
|
DbgCorDebugInternalRuntimeImpl internalRuntime;
|
||
|
public override DbgInternalRuntime CreateInternalRuntime(DbgRuntime runtime) {
|
||
|
if (internalRuntime is not null)
|
||
|
throw new InvalidOperationException();
|
||
|
return internalRuntime = new DbgCorDebugInternalRuntimeImpl(this, runtime, dmdRuntime, CorDebugRuntimeKind, dnDebugger.DebuggeeVersion ?? string.Empty, dnDebugger.CLRPath, dnDebugger.RuntimeDirectory);
|
||
|
}
|
||
|
|
||
|
public override void OnConnected(DbgObjectFactory objectFactory, DbgRuntime runtime) {
|
||
|
Debug.Assert(objectFactory.Runtime == runtime);
|
||
|
Debug.Assert(Array.IndexOf(objectFactory.Process.Runtimes, runtime) < 0);
|
||
|
this.objectFactory = objectFactory;
|
||
|
runtime.GetOrCreateData(() => new RuntimeData(this));
|
||
|
if (redirectConsoleOutput)
|
||
|
CorDebugThread(() => dnDebugger.OnRedirectedOutput += DnDebugger_OnRedirectedOutput);
|
||
|
}
|
||
|
|
||
|
protected override void CloseCore(DbgDispatcher dispatcher) {
|
||
|
UnhookDnDebuggerEventsAndCloseProcessHandle();
|
||
|
debuggerThread.Terminate();
|
||
|
DnDebuggerObjectHolder[] objHoldersToClose;
|
||
|
lock (lockObj) {
|
||
|
framesBuffer = null;
|
||
|
toEngineAppDomain.Clear();
|
||
|
toEngineModule.Clear();
|
||
|
toDynamicModuleHelper.Clear();
|
||
|
toEngineThread.Clear();
|
||
|
objHoldersToClose = objectHolders.ToArray();
|
||
|
objectHolders.Clear();
|
||
|
}
|
||
|
foreach (var obj in objHoldersToClose)
|
||
|
obj.Dispose();
|
||
|
}
|
||
|
|
||
|
bool HasConnected_DebugThread {
|
||
|
get {
|
||
|
debuggerThread.VerifyAccess();
|
||
|
// If it's null, we haven't connected yet (most likely due to timeout, eg. trying to debug
|
||
|
// a .NET Framework program with the .NET engine)
|
||
|
return dnDebugger is not null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public override void Break() => CorDebugThread(BreakCore);
|
||
|
void BreakCore() {
|
||
|
debuggerThread.VerifyAccess();
|
||
|
if (!HasConnected_DebugThread)
|
||
|
return;
|
||
|
|
||
|
// If we haven't gotten the CreateProcess event yet, wait for it.
|
||
|
if (dnDebugger.ProcessState == DebuggerProcessState.Starting)
|
||
|
return;
|
||
|
|
||
|
if (dnDebugger.ProcessState == DebuggerProcessState.Running) {
|
||
|
int hr = dnDebugger.TryBreakProcesses();
|
||
|
if (hr < 0) {
|
||
|
// We also sometimes get 0x80070005 before the process is terminated
|
||
|
if (hr == CordbgErrors.CORDBG_E_PROCESS_TERMINATED)
|
||
|
return;
|
||
|
SendMessage(new DbgMessageBreak(string.Format(dnSpy_Debugger_DotNet_CorDebug_Resources.Error_CouldNotBreakProcess, hr), GetMessageFlags()));
|
||
|
}
|
||
|
else {
|
||
|
Debug.Assert(dnDebugger.ProcessState == DebuggerProcessState.Paused);
|
||
|
// The debugger just picks the first thread in the first AppDomain, and this isn't
|
||
|
// always the main thread, eg. when we've attached to a proces. It also doesn't
|
||
|
// have enough info to find the main thread so we have to do it.
|
||
|
SendMessage(new DbgMessageBreak(GetThreadPreferMain_CorDebug(), GetMessageFlags()));
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
SendMessage(new DbgMessageBreak(GetThreadPreferMain_CorDebug(), GetMessageFlags()));
|
||
|
}
|
||
|
|
||
|
DbgThread? GetThreadPreferMain_CorDebug() {
|
||
|
debuggerThread.VerifyAccess();
|
||
|
DbgThread? firstThread = null;
|
||
|
foreach (var p in dnDebugger.Processes) {
|
||
|
foreach (var t in p.Threads) {
|
||
|
var thread = TryGetThread(t);
|
||
|
if (firstThread is null)
|
||
|
firstThread = thread;
|
||
|
if (thread?.IsMain == true)
|
||
|
return thread;
|
||
|
}
|
||
|
}
|
||
|
return firstThread;
|
||
|
}
|
||
|
|
||
|
public override void Run() => CorDebugThread(RunCore);
|
||
|
void RunCore() {
|
||
|
debuggerThread.VerifyAccess();
|
||
|
if (!HasConnected_DebugThread)
|
||
|
return;
|
||
|
if (dnDebugger.ProcessState == DebuggerProcessState.Paused)
|
||
|
Continue_CorDebug();
|
||
|
}
|
||
|
|
||
|
internal void Continue_CorDebug() {
|
||
|
debuggerThread.VerifyAccess();
|
||
|
ClrDacRunning?.Invoke(this, EventArgs.Empty);
|
||
|
// We could be func evaluating and get a CreateThread event. The DbgManager will call Run()
|
||
|
// but we mustn't dispose of the handles that we're still using.
|
||
|
if (!dnDebugger.IsEvaluating) {
|
||
|
SetReturnValues(Array.Empty<DbgDotNetReturnValueInfo>());
|
||
|
CloseDotNetValues_CorDebug();
|
||
|
}
|
||
|
dnDebugger.Continue();
|
||
|
}
|
||
|
|
||
|
public override void Terminate() => CorDebugThread(TerminateCore);
|
||
|
void TerminateCore() {
|
||
|
debuggerThread.VerifyAccess();
|
||
|
if (!HasConnected_DebugThread)
|
||
|
return;
|
||
|
if (dnDebugger.ProcessState != DebuggerProcessState.Terminated)
|
||
|
dnDebugger.TerminateProcesses();
|
||
|
}
|
||
|
|
||
|
public override bool CanDetach => true;
|
||
|
|
||
|
public override void Detach() => CorDebugThread(DetachCore);
|
||
|
void DetachCore() {
|
||
|
debuggerThread.VerifyAccess();
|
||
|
if (!HasConnected_DebugThread)
|
||
|
return;
|
||
|
if (dnDebugger.ProcessState != DebuggerProcessState.Terminated) {
|
||
|
int hr = dnDebugger.TryDetach();
|
||
|
if (hr < 0) {
|
||
|
Debug.Assert(hr == CordbgErrors.CORDBG_E_UNRECOVERABLE_ERROR || hr == CordbgErrors.CORDBG_E_PROCESS_NOT_SYNCHRONIZED);
|
||
|
dnDebugger.TerminateProcesses();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal DnDebuggerObjectHolder<T> CreateDnDebuggerObjectHolder<T>(T obj) where T : class {
|
||
|
var res = DnDebuggerObjectHolderImpl<T>.Create_DONT_CALL(this, obj);
|
||
|
lock (lockObj)
|
||
|
objectHolders.Add(res);
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
internal void Remove<T>(DnDebuggerObjectHolder<T> obj) where T : class {
|
||
|
lock (lockObj) {
|
||
|
bool b = objectHolders.Remove(obj);
|
||
|
Debug.Assert(b);
|
||
|
}
|
||
|
obj.Dispose();
|
||
|
}
|
||
|
|
||
|
public override DbgEngineStepper CreateStepper(DbgThread thread) =>
|
||
|
dbgEngineStepperFactory.Create(DotNetRuntime, new DbgDotNetEngineStepperImpl(this, dnDebugger), thread);
|
||
|
}
|
||
|
}
|