484 lines
18 KiB
C#
Raw Normal View History

2021-09-20 18:20:01 +02:00
/*
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.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using dndbg.COM.CorDebug;
using dndbg.Engine;
using dnSpy.Contracts.Debugger;
using dnSpy.Contracts.Debugger.Code;
using dnSpy.Contracts.Debugger.DotNet.Code;
using dnSpy.Contracts.Debugger.DotNet.CorDebug.Code;
using dnSpy.Contracts.Debugger.Engine;
using dnSpy.Contracts.Debugger.Engine.CallStack;
using dnSpy.Contracts.Debugger.Evaluation;
using dnSpy.Contracts.Metadata;
using dnSpy.Debugger.DotNet.CorDebug.CallStack;
using dnSpy.Debugger.DotNet.CorDebug.DAC;
using dnSpy.Debugger.DotNet.CorDebug.Properties;
using dnSpy.Debugger.DotNet.Metadata;
namespace dnSpy.Debugger.DotNet.CorDebug.Impl {
abstract partial class DbgEngineImpl {
sealed class DbgThreadData {
public DnThread DnThread { get; }
public ThreadProperties? Last { get; set; }
public bool HasNewName { get; set; }
public bool IsMainThread { get; set; }
public DbgThreadData(DnThread dnThread, bool isMainThread) {
DnThread = dnThread ?? throw new ArgumentNullException(nameof(dnThread));
IsMainThread = isMainThread;
}
}
DbgThreadData? TryGetThreadData(DbgThread thread) {
if (thread is not null && thread.TryGetData(out DbgThreadData? data))
return data;
return null;
}
internal DnThread GetThread(DbgThread thread) =>
TryGetThreadData(thread)?.DnThread ?? throw new InvalidOperationException();
ThreadProperties GetThreadProperties_CorDebug(DnThread thread, ThreadProperties? oldProperties, bool isCreateThread, bool forceReadName, bool isMainThread) {
debuggerThread.VerifyAccess();
var appDomain = TryGetEngineAppDomain(thread.AppDomain)?.AppDomain;
ulong id = (uint)thread.VolatileThreadId;
// If it was created, it could already have a new name so always try to read it
if (isCreateThread)
forceReadName = true;
var managedId = oldProperties?.ManagedId;
// Always retry calling ClrDac since it sometimes fails the first time we call it
if (managedId is null)
managedId = GetManagedId_ClrDac(thread);
// If we should read the name, it means the CLR thread object is available so we
// should also read the managed ID.
if (managedId is null && forceReadName)
managedId = GetManagedId(thread, appDomain);
// If it's a new thread, it has no name (m_Name is null)
var name = forceReadName ? GetThreadName(thread, appDomain) : oldProperties?.Name;
int suspendedCount = thread.CorThread.IsSuspended ? 1 : 0;
var userState = thread.CorThread.UserState;
var kind = GetThreadKind(thread, isMainThread);
return new ThreadProperties(appDomain, kind, id, managedId, name, suspendedCount, userState);
}
static CorValue? TryGetThreadObject(DnThread thread) {
var threadObj = thread.CorThread.Object;
if (threadObj is null || threadObj.IsNull)
return null;
threadObj = threadObj.GetDereferencedValue(out int hr);
if (threadObj is null || threadObj.IsNull)
return null;
return threadObj;
}
ulong? GetManagedId(DnThread thread, DbgAppDomain? appDomain) {
var res = ReadField_CorDebug(TryGetThreadObject(thread), appDomain, KnownMemberNames.Thread_ManagedThreadId_FieldName1, KnownMemberNames.Thread_ManagedThreadId_FieldName2);
if (res is null || !res.HasValue)
return null;
if (res.Value.ValueType == DbgSimpleValueType.Int32)
return (uint)(int?)res.Value.RawValue!;
if (res.Value.ValueType == DbgSimpleValueType.UInt32)
return (uint?)res.Value.RawValue;
return null;
}
ulong? GetManagedId_ClrDac(DnThread thread) {
Debug.Assert(clrDacInitd);
var info = clrDac.GetThreadInfo(thread.VolatileThreadId);
if (info is null)
return null;
return (uint)info.Value.ManagedThreadId;
}
string? GetThreadName(DnThread thread, DbgAppDomain? appDomain) {
var res = ReadField_CorDebug(TryGetThreadObject(thread), appDomain, KnownMemberNames.Thread_Name_FieldName1, KnownMemberNames.Thread_Name_FieldName2);
if (res is null || !res.HasValue)
return null;
if (res.Value.ValueType == DbgSimpleValueType.StringUtf16)
return (string?)res.Value.RawValue;
return null;
}
string GetThreadKind(DnThread thread, bool isMainThread) {
if (isMainThread)
return PredefinedThreadKinds.Main;
var s = GetThreadKind_ClrDac(thread);
if (s is not null)
return s;
if ((thread.CorThread.UserState & CorDebugUserState.USER_STOPPED) != 0)
return PredefinedThreadKinds.Terminated;
if ((thread.CorThread.UserState & CorDebugUserState.USER_THREADPOOL) != 0)
return PredefinedThreadKinds.ThreadPool;
return PredefinedThreadKinds.Unknown;
}
string? GetThreadKind_ClrDac(DnThread thread) {
Debug.Assert(clrDacInitd);
var tmp = clrDac.GetThreadInfo(thread.VolatileThreadId);
if (tmp is null)
return null;
var info = tmp.Value;
if (!info.IsAlive)
return PredefinedThreadKinds.Terminated;
if (info.IsGC)
return PredefinedThreadKinds.GC;
if (info.IsFinalizer)
return PredefinedThreadKinds.Finalizer;
const DAC.ClrDacThreadFlags threadPoolBits = DAC.ClrDacThreadFlags.IsThreadpoolCompletionPort |
DAC.ClrDacThreadFlags.IsThreadpoolGate | DAC.ClrDacThreadFlags.IsThreadpoolTimer |
DAC.ClrDacThreadFlags.IsThreadpoolWait | DAC.ClrDacThreadFlags.IsThreadpoolWorker;
if ((info.Flags & threadPoolBits) != 0)
return PredefinedThreadKinds.ThreadPool;
return PredefinedThreadKinds.WorkerThread;
}
(DbgEngineThread engineThread, DbgEngineThread.UpdateOptions updateOptions, ThreadProperties props)? UpdateThreadProperties_CorDebug(DnThread thread) {
debuggerThread.VerifyAccess();
var engineThread = TryGetEngineThread(thread);
Debug2.Assert(engineThread is not null);
if (engineThread is null)
return null;
return UpdateThreadProperties_CorDebug(engineThread);
}
(DbgEngineThread engineThread, DbgEngineThread.UpdateOptions updateOptions, ThreadProperties props)? UpdateThreadProperties_CorDebug(DbgEngineThread engineThread) {
debuggerThread.VerifyAccess();
var threadData = engineThread.Thread.GetData<DbgThreadData>();
var newProps = GetThreadProperties_CorDebug(threadData.DnThread, threadData.Last, isCreateThread: false, forceReadName: threadData.HasNewName, isMainThread: threadData.IsMainThread);
threadData.HasNewName = false;
var updateOptions = newProps.Compare(threadData.Last);
if (updateOptions == DbgEngineThread.UpdateOptions.None)
return null;
threadData.Last = newProps;
return (engineThread, updateOptions, newProps);
}
void NotifyThreadPropertiesChanged_CorDebug(DbgEngineThread engineThread, DbgEngineThread.UpdateOptions updateOptions, ThreadProperties props) {
debuggerThread.VerifyAccess();
ReadOnlyCollection<DbgStateInfo>? state = null;
if ((updateOptions & DbgEngineThread.UpdateOptions.State) != 0)
state = DnThreadUtils.GetState(props.UserState);
engineThread.Update(updateOptions, appDomain: props.AppDomain, kind: props.Kind, id: props.Id, managedId: props.ManagedId, name: props.Name, suspendedCount: props.SuspendedCount, state: state);
}
void UpdateThreadProperties_CorDebug() {
debuggerThread.VerifyAccess();
List<(DbgEngineThread engineThread, DbgEngineThread.UpdateOptions updateOptions, ThreadProperties props)>? threadsToUpdate = null;
KeyValuePair<DnThread, DbgEngineThread>[] infos;
lock (lockObj)
infos = toEngineThread.ToArray();
foreach (var kv in infos) {
var info = UpdateThreadProperties_CorDebug(kv.Value);
if (info is null)
continue;
if (threadsToUpdate is null)
threadsToUpdate = new List<(DbgEngineThread, DbgEngineThread.UpdateOptions, ThreadProperties)>();
threadsToUpdate.Add(info.Value);
}
if (threadsToUpdate is not null) {
foreach (var info in threadsToUpdate)
NotifyThreadPropertiesChanged_CorDebug(info.engineThread, info.updateOptions, info.props);
}
}
void DnDebugger_OnThreadAdded(object? sender, ThreadDebuggerEventArgs e) {
Debug2.Assert(objectFactory is not null);
if (e.Added) {
bool isMainThread = IsMainThread(e.Thread);
var props = GetThreadProperties_CorDebug(e.Thread, null, isCreateThread: true, forceReadName: false, isMainThread: isMainThread);
var threadData = new DbgThreadData(e.Thread, isMainThread) { Last = props };
var state = DnThreadUtils.GetState(props.UserState);
e.ShouldPause = true;
var engineThread = objectFactory.CreateThread(props.AppDomain, props.Kind, props.Id, props.ManagedId, props.Name, props.SuspendedCount, state, GetMessageFlags(), data: threadData);
lock (lockObj)
toEngineThread.Add(e.Thread, engineThread);
}
else {
DbgEngineThread? engineThread;
lock (lockObj) {
if (toEngineThread.TryGetValue(e.Thread, out engineThread))
toEngineThread.Remove(e.Thread);
}
if (engineThread is not null) {
e.ShouldPause = true;
engineThread.Remove(GetMessageFlags());
}
}
}
bool IsMainThread(DnThread thread) {
// We'll detect this later
if (StartKind == DbgStartKind.Attach)
return false;
if (alreadyKnowsMainThread)
return false;
if (IsNotMainThread(thread))
return false;
alreadyKnowsMainThread = true;
return true;
}
bool alreadyKnowsMainThread;
bool IsNotMainThread(DnThread thread) {
var info = clrDac.GetThreadInfo(thread.VolatileThreadId);
if (info is not null) {
var flags = info.Value.Flags;
const ClrDacThreadFlags NotMainThreadFlags =
ClrDacThreadFlags.IsFinalizer |
ClrDacThreadFlags.IsGC |
ClrDacThreadFlags.IsDebuggerHelper |
ClrDacThreadFlags.IsThreadpoolTimer |
ClrDacThreadFlags.IsThreadpoolCompletionPort |
ClrDacThreadFlags.IsThreadpoolWorker |
ClrDacThreadFlags.IsThreadpoolWait |
ClrDacThreadFlags.IsThreadpoolGate;
if ((flags & NotMainThreadFlags) != 0)
return true;
}
return false;
}
void DetectMainThread() {
debuggerThread.VerifyAccess();
Debug.Assert(StartKind == DbgStartKind.Attach);
if (alreadyKnowsMainThread)
return;
(DnThread thread, DbgThreadData data) mainThreadInfo = default;
lock (lockObj) {
var list = new List<(DnThread thread, DbgThreadData data)>();
foreach (var kv in toEngineThread) {
var thread = kv.Key;
if (IsNotMainThread(thread))
continue;
var data = kv.Value.Thread.GetData<DbgThreadData>();
list.Add((thread, data));
}
if (list.Count > 0) {
const ulong defaultManagedId = ulong.MaxValue;
// The main thread should have a low managed ID, very often MID=1
list.Sort((a, b) => (a.data.Last?.ManagedId ?? defaultManagedId).CompareTo(b.data.Last?.ManagedId ?? defaultManagedId));
alreadyKnowsMainThread = true;
foreach (var threadInfo in list) {
if (IsMainThreadCheckCallStack(threadInfo.thread)) {
mainThreadInfo = threadInfo;
break;
}
}
}
}
if (mainThreadInfo.thread is not null) {
mainThreadInfo.data!.IsMainThread = true;
var info = UpdateThreadProperties_CorDebug(mainThreadInfo.thread);
if (info is not null)
NotifyThreadPropertiesChanged_CorDebug(info.Value.engineThread, info.Value.updateOptions, info.Value.props);
}
}
bool IsMainThreadCheckCallStack(DnThread dnThread) {
var lastFrame = dnThread.AllFrames.LastOrDefault();
var func = lastFrame?.Function;
var corModule = func?.Module;
if (corModule is null)
return false;
var module = TryGetModule(corModule);
if (module is null)
return false;
if (!module.IsExe)
return false;
var reflectionModule = module.GetReflectionModule();
if (reflectionModule is null)
return false;
var ep = reflectionModule.Assembly.EntryPoint;
if (ep?.Module != reflectionModule || ep?.MetadataToken != func?.Token)
return false;
// Seems to be the main assembly
return true;
}
DbgEngineThread? TryGetEngineThread(DnThread? thread) {
if (thread is null)
return null;
DbgEngineThread? engineThread;
bool b;
lock (lockObj)
b = toEngineThread.TryGetValue(thread, out engineThread);
Debug.Assert(b);
return engineThread;
}
void OnNewThreadName_CorDebug(DnThread? thread) {
debuggerThread.VerifyAccess();
var engineThread = TryGetEngineThread(thread);
if (engineThread is null)
return;
engineThread.Thread.GetData<DbgThreadData>().HasNewName = true;
}
public override void Freeze(DbgThread thread) => CorDebugThread(() => FreezeThawCore(thread, freeze: true));
public override void Thaw(DbgThread thread) => CorDebugThread(() => FreezeThawCore(thread, freeze: false));
void FreezeThawCore(DbgThread thread, bool freeze) {
debuggerThread.VerifyAccess();
if (!HasConnected_DebugThread)
return;
if (dnDebugger.ProcessState != DebuggerProcessState.Paused)
return;
var threadData = TryGetThreadData(thread);
if (threadData is null)
return;
var corThread = threadData.DnThread.CorThread;
if (freeze) {
if (corThread.IsSuspended)
return;
corThread.State = CorDebugThreadState.THREAD_SUSPEND;
}
else {
if (!corThread.IsSuspended)
return;
corThread.State = CorDebugThreadState.THREAD_RUN;
}
var info = UpdateThreadProperties_CorDebug(threadData.DnThread);
if (info is not null)
NotifyThreadPropertiesChanged_CorDebug(info.Value.engineThread, info.Value.updateOptions, info.Value.props);
}
public override DbgEngineStackWalker CreateStackWalker(DbgThread thread) {
var threadData = TryGetThreadData(thread);
var engineThread = TryGetEngineThread(threadData?.DnThread);
if (engineThread is null || threadData!.DnThread.HasExited)
return new NullDbgEngineStackWalker();
return new DbgEngineStackWalkerImpl(dbgDotNetNativeCodeLocationFactory, dbgDotNetCodeLocationFactory, this, threadData.DnThread, thread, GetFramesBuffer());
}
const int framesBufferSize = 0x100;
ICorDebugFrame[]? framesBuffer = new ICorDebugFrame[framesBufferSize];
ICorDebugFrame[] GetFramesBuffer() => Interlocked.Exchange(ref framesBuffer, null) ?? new ICorDebugFrame[framesBufferSize];
internal void ReturnFramesBuffer(ref ICorDebugFrame[]? framesBuffer) {
Debug2.Assert(framesBuffer is not null);
Interlocked.Exchange(ref this.framesBuffer, framesBuffer);
framesBuffer = null;
}
public override void SetIP(DbgThread thread, DbgCodeLocation location) =>
CorDebugThread(() => SetIP_CorDebug(thread, location));
void SetIP_CorDebug(DbgThread thread, DbgCodeLocation location) {
debuggerThread.VerifyAccess();
bool framesInvalidated = false;
if (TryGetFrameForSetIP_CorDebug(thread, location, out var corFrame, out uint offset) is string error) {
SendMessage(new DbgMessageSetIPComplete(thread, framesInvalidated, messageFlags: GetMessageFlags(), error: error));
return;
}
Debug2.Assert(corFrame is not null);
framesInvalidated = true;
bool failed = !corFrame.SetILFrameIP(offset);
if (failed) {
SendMessage(new DbgMessageSetIPComplete(thread, framesInvalidated, messageFlags: GetMessageFlags(), error: dnSpy_Debugger_DotNet_CorDebug_Resources.Error_CouldNotSetNextStatement));
return;
}
SendMessage(new DbgMessageSetIPComplete(thread, framesInvalidated, messageFlags: GetMessageFlags(), error: null));
}
public override bool CanSetIP(DbgThread thread, DbgCodeLocation location) =>
InvokeCorDebugThread(() => CanSetIP_CorDebug(thread, location));
bool CanSetIP_CorDebug(DbgThread thread, DbgCodeLocation location) => TryGetFrameForSetIP_CorDebug(thread, location, out var corFrame, out uint offset) is null;
string? TryGetFrameForSetIP_CorDebug(DbgThread thread, DbgCodeLocation location, out CorFrame? corFrame, out uint offset) {
debuggerThread.VerifyAccess();
corFrame = null;
offset = 0;
if (dnDebugger.ProcessState != DebuggerProcessState.Paused)
return dnSpy_Debugger_DotNet_CorDebug_Resources.Error_CouldNotSetNextStatement;
if (!TryGetFrame(thread, out corFrame))
return dnSpy_Debugger_DotNet_CorDebug_Resources.Error_CouldNotSetNextStatement;
if (!TryGetLocation(location, out var moduleId, out uint token, out offset))
return dnSpy_Debugger_DotNet_CorDebug_Resources.Error_CouldNotSetNextStatement;
var frameModuleId = TryGetModuleId(corFrame);
if (frameModuleId is null || frameModuleId.Value.ToModuleId() != moduleId || corFrame.Token != token)
return dnSpy_Debugger_DotNet_CorDebug_Resources.Error_CouldNotSetNextStatement;
return null;
}
internal DnModuleId? TryGetModuleId(CorFrame frame) => dnDebugger.TryGetModuleId(frame.Function?.Module);
bool TryGetFrame(DbgThread thread, [NotNullWhen(true)] out CorFrame? frame) {
debuggerThread.VerifyAccess();
frame = null;
var data = TryGetThreadData(thread);
if (data is null)
return false;
if (data.DnThread.HasExited)
return false;
frame = data.DnThread.AllFrames.FirstOrDefault();
if (frame is null || !frame.IsILFrame)
return false;
return true;
}
static bool TryGetLocation(DbgCodeLocation location, out ModuleId moduleId, out uint token, out uint offset) {
switch (location) {
case DbgDotNetCodeLocation dnLoc:
moduleId = dnLoc.Module;
token = dnLoc.Token;
offset = dnLoc.Offset;
return true;
case DbgDotNetNativeCodeLocation nativeLoc:
if (nativeLoc.ILOffsetMapping != DbgILOffsetMapping.Exact && nativeLoc.ILOffsetMapping != DbgILOffsetMapping.Approximate)
break;
moduleId = nativeLoc.Module;
token = nativeLoc.Token;
offset = nativeLoc.Offset;
return true;
}
moduleId = default;
token = 0;
offset = 0;
return false;
}
}
}