2021-09-20 18:20:01 +02:00

244 lines
8.8 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.ObjectModel;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.Linq;
using dnlib.DotNet;
using dnSpy.Contracts.Debugger.DotNet.Metadata;
using dnSpy.Contracts.Decompiler;
using dnSpy.Contracts.Documents;
using dnSpy.Contracts.Documents.Tabs;
using dnSpy.Contracts.Documents.Tabs.DocViewer;
using dnSpy.Contracts.Metadata;
using dnSpy.Debugger.DotNet.Metadata;
using dnSpy.Debugger.DotNet.UI;
namespace dnSpy.Debugger.DotNet.Code {
abstract class DotNetReferenceNavigator : ReferenceNavigator {
public const uint EPILOG = 0xFFFFFFFF;
public const uint PROLOG = 0xFFFFFFFE;
public abstract void GoToLocation(IDocumentTab tab, MethodDef method, ModuleTokenId module, uint offset, bool newTab);
}
[ExportReferenceNavigator]
[Export(typeof(DotNetReferenceNavigator))]
sealed class DotNetReferenceNavigatorImpl : DotNetReferenceNavigator {
readonly UIDispatcher uiDispatcher;
readonly IDocumentTabService documentTabService;
readonly Lazy<DbgMetadataService> dbgMetadataService;
[ImportingConstructor]
DotNetReferenceNavigatorImpl(UIDispatcher uiDispatcher, IDocumentTabService documentTabService, Lazy<DbgMetadataService> dbgMetadataService) {
this.uiDispatcher = uiDispatcher;
this.documentTabService = documentTabService;
this.dbgMetadataService = dbgMetadataService;
}
public override bool GoTo(object reference, ReadOnlyCollection<object> options) {
switch (reference) {
case DotNetMethodBodyReference bodyRef:
GoTo(bodyRef, options);
// Always return true even if GoTo() fails (eg. module not found). Otherwise an empty
// page will be shown when the default handler tries to show the reference.
return true;
case DotNetTokenReference tokenRef:
GoTo(tokenRef, options);
// Always return true, see comment above
return true;
}
return false;
}
ModuleDef? GetModule(ModuleId module, ReadOnlyCollection<object> options) =>
dbgMetadataService.Value.TryGetMetadata(module, DbgLoadModuleOptions.None);
bool GoTo(DotNetMethodBodyReference bodyRef, ReadOnlyCollection<object> options) {
bool newTab = options.Any(a => StringComparer.Ordinal.Equals(PredefinedReferenceNavigatorOptions.NewTab, a));
var module = GetModule(bodyRef.Module, options);
if (module is null)
return false;
var method = module.ResolveToken(bodyRef.Token) as MethodDef;
if (method is null)
return false;
uint offset = bodyRef.Offset;
if (offset == DotNetMethodBodyReference.PROLOG)
offset = PROLOG;
else if (offset == DotNetMethodBodyReference.EPILOG)
offset = EPILOG;
var tab = documentTabService.GetOrCreateActiveTab();
GoToLocation(tab, method, new ModuleTokenId(bodyRef.Module, bodyRef.Token), offset, newTab);
return true;
}
bool GoTo(DotNetTokenReference tokenRef, ReadOnlyCollection<object> options) {
bool newTab = options.Any(a => StringComparer.Ordinal.Equals(PredefinedReferenceNavigatorOptions.NewTab, a));
var module = GetModule(tokenRef.Module, options);
if (module is null)
return false;
var def = module.ResolveToken(tokenRef.Token) as IMemberDef;
if (def is null)
return false;
bool found = documentTabService.DocumentTreeView.FindNode(def.Module) is not null;
if (found) {
documentTabService.FollowReference(def, newTab, true, e => {
Debug.Assert(e.Tab.UIContext is IDocumentViewer);
if (e.Success && !e.HasMovedCaret) {
MoveCaretTo(e.Tab.UIContext as IDocumentViewer, def);
e.HasMovedCaret = true;
}
});
return true;
}
// If it wasn't found, it will be added to the treeview with a slight delay, make sure our code gets executed
// after it's happened.
uiDispatcher.UIBackground(() => {
documentTabService.FollowReference(def, newTab, true, e => {
Debug.Assert(e.Tab.UIContext is IDocumentViewer);
if (e.Success && !e.HasMovedCaret) {
MoveCaretTo(e.Tab.UIContext as IDocumentViewer, def);
e.HasMovedCaret = true;
}
});
});
return true;
}
static bool MoveCaretTo(IDocumentViewer? documentViewer, IMemberDef def) {
if (documentViewer is null)
return false;
var data = documentViewer.ReferenceCollection.FirstOrNull(a => a.Data.IsDefinition && a.Data.Reference == def);
if (data is null)
return false;
documentViewer.MoveCaretToPosition(data.Value.Span.Start);
return true;
}
public override void GoToLocation(IDocumentTab tab, MethodDef method, ModuleTokenId module, uint offset, bool newTab) {
bool specialIpOffset;
if (offset == EPILOG) {
specialIpOffset = true;
var mod = dbgMetadataService.Value.TryGetMetadata(module.Module, DbgLoadModuleOptions.AutoLoaded);
if (mod?.ResolveToken(module.Token) is MethodDef md && md.Body is not null && md.Body.Instructions.Count > 0)
offset = md.Body.Instructions[md.Body.Instructions.Count - 1].Offset;
else
return;
}
else if (offset == PROLOG) {
specialIpOffset = true;
offset = 0;
}
else
specialIpOffset = false;
GoToLocationCore(tab, method, module, offset, specialIpOffset, newTab, canRefreshMethods: true);
}
void GoToLocationCore(IDocumentTab tab, MethodDef method, ModuleTokenId module, uint offset, bool specialIpOffset, bool newTab, bool canRefreshMethods) {
uiDispatcher.VerifyAccess();
if (tab is null || method is null)
return;
// The file could've been added lazily to the list so add a short delay before we select it
uiDispatcher.UIBackground(() => {
tab.FollowReference(method, newTab, e => {
Debug.Assert(e.Tab.UIContext is IDocumentViewer);
if (e.Success && !e.HasMovedCaret) {
MoveCaretToCurrentStatement(e.Tab.UIContext as IDocumentViewer, method, module, offset, specialIpOffset, canRefreshMethods, newTab: false);
e.HasMovedCaret = true;
}
});
});
}
bool MoveCaretToCurrentStatement(IDocumentViewer? documentViewer, MethodDef method, ModuleTokenId module, uint offset, bool specialIpOffset, bool canRefreshMethods, bool newTab) {
if (documentViewer is null)
return false;
if (MoveCaretTo(documentViewer, module, offset))
return true;
if (!canRefreshMethods)
return false;
RefreshMethodBodies(documentViewer, method, module, offset, specialIpOffset, newTab: false);
return false;
}
static bool MoveCaretTo(IDocumentViewer documentViewer, ModuleTokenId module, uint offset) {
if (documentViewer is null)
return false;
if (!VerifyAndGetCurrentDebuggedMethod(documentViewer, module, out var methodDebugService))
return false;
var sourceStatement = methodDebugService.TryGetMethodDebugInfo(module)!.GetSourceStatementByCodeOffset(offset);
if (sourceStatement is null)
return false;
documentViewer.MoveCaretToPosition(sourceStatement.Value.TextSpan.Start);
return true;
}
static bool VerifyAndGetCurrentDebuggedMethod(IDocumentViewer documentViewer, ModuleTokenId token, out IMethodDebugService methodDebugService) {
methodDebugService = documentViewer.GetMethodDebugService();
return methodDebugService.TryGetMethodDebugInfo(token) is not null;
}
void RefreshMethodBodies(IDocumentViewer documentViewer, MethodDef method, ModuleTokenId module, uint offset, bool specialIpOffset, bool newTab) {
// If it's in the prolog/epilog, ignore it
if (specialIpOffset)
return;
if (module.Module.IsDynamic)
return;
var body = method.Body;
if (body is null)
return;
// If the offset is a valid instruction in the body, the method is probably not encrypted
if (body.Instructions.Any(i => i.Offset == offset))
return;
var modNode = documentTabService.DocumentTreeView.FindNode(method.Module);
if (modNode is null)
return;
if (modNode.Document is MemoryModuleDefDocument memFile)
memFile.UpdateMemory();
else {
var mod = dbgMetadataService.Value.TryGetMetadata(module.Module, DbgLoadModuleOptions.ForceMemory | DbgLoadModuleOptions.AutoLoaded);
var md = mod?.ResolveToken(module.Token) as MethodDef;
if (md is null)
return;
method = md;
}
GoToLocationCore(documentViewer.DocumentTab!, method, module, offset, specialIpOffset, newTab, canRefreshMethods: false);
}
}
}