207 lines
8.4 KiB
C#
207 lines
8.4 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.ComponentModel.Composition;
|
||
|
using System.Diagnostics;
|
||
|
using System.Threading;
|
||
|
using dnlib.DotNet;
|
||
|
using dnSpy.Contracts.Debugger;
|
||
|
using dnSpy.Contracts.Debugger.DotNet.Code;
|
||
|
using dnSpy.Contracts.Debugger.DotNet.Metadata;
|
||
|
using dnSpy.Contracts.Decompiler;
|
||
|
using dnSpy.Contracts.Metadata;
|
||
|
using dnSpy.Decompiler.Utils;
|
||
|
|
||
|
namespace dnSpy.Debugger.DotNet.Code {
|
||
|
readonly struct MethodDebugInfoResult {
|
||
|
public int MethodVersion { get; }
|
||
|
public DbgMethodDebugInfo? DebugInfo { get; }
|
||
|
public DbgMethodDebugInfo? StateMachineDebugInfo { get; }
|
||
|
public MethodDebugInfoResult(int methodVersion, DbgMethodDebugInfo? debugInfo, DbgMethodDebugInfo? stateMachineDebugInfo) {
|
||
|
if (methodVersion < 1)
|
||
|
throw new ArgumentOutOfRangeException(nameof(methodVersion));
|
||
|
MethodVersion = methodVersion;
|
||
|
DebugInfo = debugInfo;
|
||
|
StateMachineDebugInfo = stateMachineDebugInfo;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
abstract class DbgMethodDebugInfoProvider {
|
||
|
public abstract MethodDebugInfoResult GetMethodDebugInfo(IDecompiler decompiler, DbgModule module, uint token, CancellationToken cancellationToken);
|
||
|
public abstract MethodDebugInfoResult GetMethodDebugInfo(DbgRuntime runtime, IDecompiler decompiler, IDbgDotNetCodeLocation location, CancellationToken cancellationToken);
|
||
|
}
|
||
|
|
||
|
[Export(typeof(DbgMethodDebugInfoProvider))]
|
||
|
sealed class DbgMethodDebugInfoProviderImpl : DbgMethodDebugInfoProvider {
|
||
|
const DbgLoadModuleOptions loadModuleOptions = DbgLoadModuleOptions.AutoLoaded;
|
||
|
const int MAX_CACHED_DEBUG_INFOS = 5;
|
||
|
readonly DbgMetadataService dbgMetadataService;
|
||
|
|
||
|
[ImportingConstructor]
|
||
|
DbgMethodDebugInfoProviderImpl(DbgMetadataService dbgMetadataService) => this.dbgMetadataService = dbgMetadataService;
|
||
|
|
||
|
public override MethodDebugInfoResult GetMethodDebugInfo(IDecompiler decompiler, DbgModule module, uint token, CancellationToken cancellationToken) {
|
||
|
var mdModule = dbgMetadataService.TryGetMetadata(module, loadModuleOptions);
|
||
|
var key = new MethodDebugInfoResultKey(module, token);
|
||
|
return GetMethodDebugInfo(module.Runtime, key, decompiler, mdModule, token, cancellationToken);
|
||
|
}
|
||
|
|
||
|
public override MethodDebugInfoResult GetMethodDebugInfo(DbgRuntime runtime, IDecompiler decompiler, IDbgDotNetCodeLocation location, CancellationToken cancellationToken) {
|
||
|
ModuleDef? mdModule;
|
||
|
MethodDebugInfoResultKey key;
|
||
|
if (location.DbgModule is DbgModule dbgModule) {
|
||
|
key = new MethodDebugInfoResultKey(dbgModule, location.Token);
|
||
|
mdModule = dbgMetadataService.TryGetMetadata(dbgModule, loadModuleOptions);
|
||
|
}
|
||
|
else {
|
||
|
key = new MethodDebugInfoResultKey(location.Module, location.Token);
|
||
|
mdModule = dbgMetadataService.TryGetMetadata(location.Module, loadModuleOptions);
|
||
|
}
|
||
|
return GetMethodDebugInfo(runtime, key, decompiler, mdModule, location.Token, cancellationToken);
|
||
|
}
|
||
|
|
||
|
readonly struct MethodDebugInfoResultKey {
|
||
|
readonly uint token;
|
||
|
readonly DbgModule? module;
|
||
|
readonly ModuleId moduleId;
|
||
|
readonly int refreshedVersion;
|
||
|
|
||
|
public MethodDebugInfoResultKey(DbgModule module, uint token) {
|
||
|
this.token = token;
|
||
|
moduleId = default;
|
||
|
this.module = module;
|
||
|
refreshedVersion = module.RefreshedVersion;
|
||
|
}
|
||
|
|
||
|
public MethodDebugInfoResultKey(ModuleId moduleId, uint token) {
|
||
|
this.token = token;
|
||
|
this.moduleId = moduleId;
|
||
|
module = null;
|
||
|
refreshedVersion = 0;
|
||
|
}
|
||
|
|
||
|
public bool Equals(MethodDebugInfoResultKey other) =>
|
||
|
token == other.token &&
|
||
|
module == other.module &&
|
||
|
refreshedVersion == other.refreshedVersion &&
|
||
|
moduleId == other.moduleId;
|
||
|
}
|
||
|
|
||
|
sealed class RuntimeState {
|
||
|
public readonly object LockObj = new object();
|
||
|
public readonly List<(MethodDebugInfoResultKey key, MethodDebugInfoResult result)> DebugInfos = new List<(MethodDebugInfoResultKey key, MethodDebugInfoResult result)>(MAX_CACHED_DEBUG_INFOS);
|
||
|
}
|
||
|
|
||
|
MethodDebugInfoResult GetMethodDebugInfo(DbgRuntime runtime, in MethodDebugInfoResultKey key, IDecompiler decompiler, ModuleDef? mdModule, uint token, CancellationToken cancellationToken) {
|
||
|
Debug2.Assert(mdModule is not null);
|
||
|
if (mdModule is null)
|
||
|
return default;
|
||
|
|
||
|
var state = runtime.GetOrCreateData<RuntimeState>();
|
||
|
|
||
|
var debugInfos = state.DebugInfos;
|
||
|
lock (state.LockObj) {
|
||
|
for (int i = debugInfos.Count - 1; i >= 0; i--) {
|
||
|
var info = debugInfos[i];
|
||
|
if (info.key.Equals(key)) {
|
||
|
if ((info.result.DebugInfo is not null && info.result.DebugInfo.DebugInfoVersion != decompiler.Settings.Version) ||
|
||
|
(info.result.StateMachineDebugInfo is not null && info.result.StateMachineDebugInfo.DebugInfoVersion != decompiler.Settings.Version)) {
|
||
|
debugInfos.RemoveAt(i);
|
||
|
continue;
|
||
|
}
|
||
|
if (i != debugInfos.Count - 1) {
|
||
|
debugInfos.RemoveAt(i);
|
||
|
debugInfos.Add(info);
|
||
|
}
|
||
|
return info.result;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var result = GetMethodDebugInfoNonCached(decompiler, mdModule, token, cancellationToken);
|
||
|
if (result.DebugInfo is null)
|
||
|
return default;
|
||
|
lock (state.LockObj) {
|
||
|
if (debugInfos.Count == MAX_CACHED_DEBUG_INFOS)
|
||
|
debugInfos.RemoveAt(0);
|
||
|
debugInfos.Add((key, result));
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
MethodDebugInfoResult GetMethodDebugInfoNonCached(IDecompiler decompiler, ModuleDef mdModule, uint token, CancellationToken cancellationToken) {
|
||
|
cancellationToken.ThrowIfCancellationRequested();
|
||
|
|
||
|
var method = mdModule.ResolveToken(token) as MethodDef;
|
||
|
// Could be null if it's a dynamic assembly. It will get refreshed later and we'll get called again.
|
||
|
if (method is null)
|
||
|
return default;
|
||
|
|
||
|
if (!StateMachineHelpers.TryGetKickoffMethod(method, out var containingMethod))
|
||
|
containingMethod = method;
|
||
|
|
||
|
var decContext = new DecompilationContext {
|
||
|
CancellationToken = cancellationToken,
|
||
|
CalculateILSpans = true,
|
||
|
// This is only needed when decompiling more than one body
|
||
|
AsyncMethodBodyDecompilation = false,
|
||
|
};
|
||
|
var info = TryDecompileAndGetDebugInfo(decompiler, containingMethod, token, decContext, cancellationToken);
|
||
|
if (info.debugInfo is null && containingMethod != method) {
|
||
|
// The decompiler can't decompile the iterator / async method, try again,
|
||
|
// but only decompile the MoveNext method
|
||
|
info = TryDecompileAndGetDebugInfo(decompiler, method, token, decContext, cancellationToken);
|
||
|
}
|
||
|
if (info.debugInfo is null && method.Body is null) {
|
||
|
var scope = new DbgMethodDebugScope(new DbgILSpan(0, 0), Array.Empty<DbgMethodDebugScope>(), Array.Empty<DbgLocal>(), Array.Empty<DbgImportInfo>());
|
||
|
info = (new DbgMethodDebugInfo(DbgCompilerKind.Unknown, -1, method, null, Array.Empty<DbgSourceStatement>(), scope, null), null);
|
||
|
}
|
||
|
if (info.debugInfo is null)
|
||
|
return default;
|
||
|
|
||
|
// We don't support EnC so the version is always 1
|
||
|
const int methodVersion = 1;
|
||
|
return new MethodDebugInfoResult(methodVersion, info.debugInfo, info.stateMachineDebugInfo);
|
||
|
}
|
||
|
|
||
|
static class DecompilerOutputImplCache {
|
||
|
static DecompilerOutputImpl? instance;
|
||
|
public static DecompilerOutputImpl Alloc() => Interlocked.Exchange(ref instance, null) ?? new DecompilerOutputImpl();
|
||
|
public static void Free(ref DecompilerOutputImpl? inst) {
|
||
|
var tmp = inst!;
|
||
|
inst = null;
|
||
|
tmp.Clear();
|
||
|
instance = tmp;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
(DbgMethodDebugInfo debugInfo, DbgMethodDebugInfo? stateMachineDebugInfo) TryDecompileAndGetDebugInfo(IDecompiler decompiler, MethodDef method, uint methodToken, DecompilationContext decContext, CancellationToken cancellationToken) {
|
||
|
DecompilerOutputImpl? output = DecompilerOutputImplCache.Alloc();
|
||
|
output.Initialize(methodToken);
|
||
|
decompiler.Decompile(method, output, decContext);
|
||
|
var info = output.TryGetMethodDebugInfo();
|
||
|
DecompilerOutputImplCache.Free(ref output);
|
||
|
cancellationToken.ThrowIfCancellationRequested();
|
||
|
return info;
|
||
|
}
|
||
|
}
|
||
|
}
|