You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
430 lines
11 KiB
430 lines
11 KiB
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) |
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) |
|
|
|
using System; |
|
using System.Collections.Generic; |
|
using System.Runtime.InteropServices; |
|
using System.Threading.Tasks; |
|
using Debugger.Interop; |
|
using Debugger.Interop.CorDebug; |
|
using Debugger.Interop.CorSym; |
|
using Debugger.Interop.MetaData; |
|
using Debugger.MetaData; |
|
using ICSharpCode.NRefactory.TypeSystem; |
|
|
|
namespace Debugger |
|
{ |
|
public class Module: DebuggerObject, IDisposable |
|
{ |
|
AppDomain appDomain; |
|
Process process; |
|
|
|
bool unloaded = false; |
|
string name; |
|
string fullPath = string.Empty; |
|
|
|
int orderOfLoading = 0; |
|
ICorDebugModule corModule; |
|
ISymUnmanagedReader symReader; |
|
MetaDataImport metaData; |
|
|
|
Task<IUnresolvedAssembly> unresolvedAssembly; |
|
|
|
public IUnresolvedAssembly UnresolvedAssembly { |
|
get { return unresolvedAssembly.Result; } |
|
} |
|
|
|
public IAssembly Assembly { |
|
get { return this.UnresolvedAssembly.Resolve(appDomain.Compilation.TypeResolveContext); } |
|
} |
|
|
|
public AppDomain AppDomain { |
|
get { return appDomain; } |
|
} |
|
|
|
public Process Process { |
|
get { return process; } |
|
} |
|
|
|
public ISymbolSource SymbolSource { |
|
get { return this.Process.SymbolSource; } |
|
} |
|
|
|
NDebugger Debugger { |
|
get { return this.AppDomain.Process.Debugger; } |
|
} |
|
|
|
[Debugger.Tests.Ignore] |
|
public MetaDataImport MetaData { |
|
get { |
|
return metaData; |
|
} |
|
} |
|
|
|
public bool Unloaded { |
|
get { |
|
return unloaded; |
|
} |
|
} |
|
|
|
[Debugger.Tests.Ignore] |
|
public ISymUnmanagedReader SymReader { |
|
get { |
|
return symReader; |
|
} |
|
} |
|
|
|
[Debugger.Tests.Ignore] |
|
public ISymUnmanagedDocument[] SymDocuments { |
|
get { |
|
ISymUnmanagedDocument[] docs; |
|
uint maxCount = 2; |
|
uint fetched; |
|
do { |
|
maxCount *= 8; |
|
docs = new ISymUnmanagedDocument[maxCount]; |
|
symReader.GetDocuments(maxCount, out fetched, docs); |
|
} while (fetched == maxCount); |
|
Array.Resize(ref docs, (int)fetched); |
|
return docs; |
|
} |
|
} |
|
|
|
[Debugger.Tests.Ignore] |
|
public ICorDebugModule CorModule { |
|
get { return corModule; } |
|
} |
|
|
|
[Debugger.Tests.Ignore] |
|
public ICorDebugModule2 CorModule2 { |
|
get { return (ICorDebugModule2)corModule; } |
|
} |
|
|
|
[Debugger.Tests.Ignore] |
|
public ulong BaseAdress { |
|
get { |
|
return this.CorModule.GetBaseAddress(); |
|
} |
|
} |
|
|
|
public bool IsDynamic { |
|
get { |
|
return this.CorModule.IsDynamic() == 1; |
|
} |
|
} |
|
|
|
public bool IsInMemory { |
|
get { |
|
return this.CorModule.IsInMemory() == 1; |
|
} |
|
} |
|
|
|
internal uint AppDomainID { |
|
get { |
|
return this.CorModule.GetAssembly().GetAppDomain().GetID(); |
|
} |
|
} |
|
|
|
public string Name { |
|
get { |
|
return name; |
|
} |
|
} |
|
|
|
[Debugger.Tests.Ignore] |
|
public string FullPath { |
|
get { |
|
return fullPath; |
|
} |
|
} |
|
|
|
public bool HasSymbols { |
|
get { |
|
return symReader != null; |
|
} |
|
} |
|
|
|
public int OrderOfLoading { |
|
get { |
|
return orderOfLoading; |
|
} |
|
set { |
|
orderOfLoading = value; |
|
} |
|
} |
|
|
|
[Debugger.Tests.Ignore] |
|
public CorDebugJITCompilerFlags JITCompilerFlags |
|
{ |
|
get |
|
{ |
|
uint retval = ((ICorDebugModule2)corModule).GetJITCompilerFlags(); |
|
return (CorDebugJITCompilerFlags)retval; |
|
} |
|
set |
|
{ |
|
// ICorDebugModule2.SetJITCompilerFlags can return successful HRESULTS other than S_OK. |
|
// Since we have asked the COMInterop layer to preservesig, we need to marshal any failing HRESULTS. |
|
((ICorDebugModule2)corModule).SetJITCompilerFlags((uint)value); |
|
} |
|
} |
|
|
|
internal Module(AppDomain appDomain, ICorDebugModule corModule) |
|
{ |
|
this.appDomain = appDomain; |
|
this.process = appDomain.Process; |
|
this.corModule = corModule; |
|
|
|
unresolvedAssembly = TypeSystemExtensions.LoadModuleAsync(this, corModule); |
|
metaData = new MetaDataImport(corModule); |
|
|
|
if (IsDynamic || IsInMemory) { |
|
name = corModule.GetName(); |
|
} else { |
|
fullPath = corModule.GetName(); |
|
name = System.IO.Path.GetFileName(FullPath); |
|
} |
|
|
|
SetJITCompilerFlags(); |
|
|
|
LoadSymbolsFromDisk(process.Options.SymbolsSearchPaths); |
|
ResetJustMyCodeStatus(); |
|
LoadSymbolsDynamic(); |
|
} |
|
|
|
public void UnloadSymbols() |
|
{ |
|
if (symReader != null) { |
|
// The interface is not always supported, I did not manage to reproduce it, but the |
|
// last callbacks in the user's log were UnloadClass and UnloadModule so I guess |
|
// it has something to do with dynamic modules. |
|
if (symReader is ISymUnmanagedDispose) { |
|
((ISymUnmanagedDispose)symReader).Destroy(); |
|
} |
|
symReader = null; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Load symblos for on-disk module |
|
/// </summary> |
|
public void LoadSymbolsFromDisk(IEnumerable<string> symbolsSearchPaths) |
|
{ |
|
if (!IsDynamic && !IsInMemory) { |
|
if (symReader == null) { |
|
symReader = metaData.GetSymReader(fullPath, string.Join("; ", symbolsSearchPaths ?? new string[0])); |
|
if (symReader != null) { |
|
process.TraceMessage("Loaded symbols from disk for " + this.Name); |
|
OnSymbolsUpdated(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Load symbols for in-memory module |
|
/// </summary> |
|
public void LoadSymbolsFromMemory(IStream pSymbolStream) |
|
{ |
|
if (this.IsInMemory) { |
|
UnloadSymbols(); |
|
|
|
symReader = metaData.GetSymReader(pSymbolStream); |
|
if (symReader != null) { |
|
process.TraceMessage("Loaded symbols from memory for " + this.Name); |
|
} else { |
|
process.TraceMessage("Failed to load symbols from memory"); |
|
} |
|
|
|
OnSymbolsUpdated(); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Load symbols for dynamic module |
|
/// (as of .NET 4.0) |
|
/// </summary> |
|
public void LoadSymbolsDynamic() |
|
{ |
|
if (this.CorModule is ICorDebugModule3 && this.IsDynamic) { |
|
Guid guid = new Guid(0, 0, 0, 0xc0, 0, 0, 0, 0, 0, 0, 70); |
|
try { |
|
symReader = (ISymUnmanagedReader)((ICorDebugModule3)this.CorModule).CreateReaderForInMemorySymbols(guid); |
|
} catch (COMException e) { |
|
// 0x80131C3B The application did not supply symbols when it loaded or created this module, or they are not yet available. |
|
if ((uint)e.ErrorCode == 0x80131C3B) { |
|
process.TraceMessage("Failed to load dynamic symbols for " + this.Name); |
|
return; |
|
} |
|
throw; |
|
} |
|
TrackedComObjects.Track(symReader); |
|
process.TraceMessage("Loaded dynamic symbols for " + this.Name); |
|
OnSymbolsUpdated(); |
|
} |
|
} |
|
|
|
void OnSymbolsUpdated() |
|
{ |
|
foreach (Breakpoint b in this.Debugger.Breakpoints) { |
|
b.SetBreakpoint(this); |
|
} |
|
ResetJustMyCodeStatus(); |
|
} |
|
|
|
void SetJITCompilerFlags() |
|
{ |
|
if (Process.DebugMode != DebugModeFlag.Default) { |
|
// translate DebugModeFlags to JITCompilerFlags |
|
CorDebugJITCompilerFlags jcf = MapDebugModeToJITCompilerFlags(Process.DebugMode); |
|
|
|
try |
|
{ |
|
this.JITCompilerFlags = jcf; |
|
|
|
// Flags may succeed but not set all bits, so requery. |
|
CorDebugJITCompilerFlags jcfActual = this.JITCompilerFlags; |
|
|
|
#if DEBUG |
|
if (jcf != jcfActual) |
|
Console.WriteLine("Couldn't set all flags. Actual flags:" + jcfActual.ToString()); |
|
else |
|
Console.WriteLine("Actual flags:" + jcfActual.ToString()); |
|
#endif |
|
} |
|
catch (COMException ex) |
|
{ |
|
// we'll ignore the error if we cannot set the jit flags |
|
Console.WriteLine(string.Format("Failed to set flags with hr=0x{0:x}", ex.ErrorCode)); |
|
} |
|
} |
|
} |
|
|
|
/// <summary> Sets all code as being 'my code'. The code will be gradually |
|
/// set to not-user-code as encountered according to stepping options </summary> |
|
public void ResetJustMyCodeStatus() |
|
{ |
|
uint unused = 0; |
|
if (process.Options.StepOverNoSymbols && !this.HasSymbols) { |
|
// Optimization - set the code as non-user right away |
|
this.CorModule2.SetJMCStatus(0, 0, ref unused); |
|
return; |
|
} |
|
try { |
|
this.CorModule2.SetJMCStatus(process.Options.EnableJustMyCode ? 1 : 0, 0, ref unused); |
|
} catch (COMException e) { |
|
// Cannot use JMC on this code (likely wrong JIT settings). |
|
if ((uint)e.ErrorCode == 0x80131323) { |
|
process.TraceMessage("Cannot use JMC on this code. Release build?"); |
|
return; |
|
} |
|
throw; |
|
} |
|
} |
|
|
|
public void ApplyChanges(byte[] metadata, byte[] il) |
|
{ |
|
this.CorModule2.ApplyChanges((uint)metadata.Length, metadata, (uint)il.Length, il); |
|
} |
|
|
|
public void Dispose() |
|
{ |
|
UnloadSymbols(); |
|
unloaded = true; |
|
} |
|
|
|
public override string ToString() |
|
{ |
|
return string.Format("{0}", this.Name); |
|
} |
|
|
|
public static CorDebugJITCompilerFlags MapDebugModeToJITCompilerFlags(DebugModeFlag debugMode) |
|
{ |
|
CorDebugJITCompilerFlags jcf; |
|
switch (debugMode) |
|
{ |
|
case DebugModeFlag.Optimized: |
|
jcf = CorDebugJITCompilerFlags.CORDEBUG_JIT_DEFAULT; // DEFAULT really means force optimized. |
|
break; |
|
case DebugModeFlag.Debug: |
|
jcf = CorDebugJITCompilerFlags.CORDEBUG_JIT_DISABLE_OPTIMIZATION; |
|
break; |
|
case DebugModeFlag.Enc: |
|
jcf = CorDebugJITCompilerFlags.CORDEBUG_JIT_ENABLE_ENC; |
|
break; |
|
default: |
|
// we don't have mapping from default to "default", |
|
// therefore we'll use DISABLE_OPTIMIZATION. |
|
jcf = CorDebugJITCompilerFlags.CORDEBUG_JIT_DISABLE_OPTIMIZATION; |
|
break; |
|
} |
|
return jcf; |
|
} |
|
|
|
Dictionary<ICorDebugFunction, uint> backingFieldCache = new Dictionary<ICorDebugFunction, uint>(); |
|
|
|
/// <summary> Is this method in form 'return this.field;'? </summary> |
|
internal uint GetBackingFieldToken(ICorDebugFunction corFunction) |
|
{ |
|
uint token; |
|
if (backingFieldCache.TryGetValue(corFunction, out token)) { |
|
return token; |
|
} |
|
|
|
ICorDebugCode corCode; |
|
try { |
|
corCode = corFunction.GetILCode(); |
|
} catch (COMException) { |
|
backingFieldCache[corFunction] = 0; |
|
return 0; |
|
} |
|
|
|
if (corCode == null || corCode.IsIL() == 0 || corCode.GetSize() > 12) { |
|
backingFieldCache[corFunction] = 0; |
|
return 0; |
|
} |
|
|
|
List<byte> code = new List<byte>(corCode.GetCode()); |
|
|
|
bool success = |
|
(Read(code, 0x00) || true) && // nop || nothing |
|
(Read(code, 0x02, 0x7B) || Read(code, 0x7E)) && // ldarg.0; ldfld || ldsfld |
|
ReadToken(code, ref token) && // <field token> |
|
(Read(code, 0x0A, 0x2B, 0x00, 0x06) || true) && // stloc.0; br.s; offset+00; ldloc.0 || nothing |
|
Read(code, 0x2A); // ret |
|
|
|
if (!success) { |
|
backingFieldCache[corFunction] = 0; |
|
return 0; |
|
} |
|
|
|
backingFieldCache[corFunction] = token; |
|
return token; |
|
} |
|
|
|
// Read expected sequence of bytes |
|
static bool Read(List<byte> code, params byte[] expected) |
|
{ |
|
if (code.Count < expected.Length) |
|
return false; |
|
for(int i = 0; i < expected.Length; i++) { |
|
if (code[i] != expected[i]) |
|
return false; |
|
} |
|
code.RemoveRange(0, expected.Length); |
|
return true; |
|
} |
|
|
|
// Read field token |
|
static bool ReadToken(List<byte> code, ref uint token) |
|
{ |
|
if (code.Count < 4) |
|
return false; |
|
if (code[3] != 0x04) // field token |
|
return false; |
|
token = ((uint)code[0]) + ((uint)code[1] << 8) + ((uint)code[2] << 16) + ((uint)code[3] << 24); |
|
code.RemoveRange(0, 4); |
|
return true; |
|
} |
|
} |
|
}
|
|
|