Cosmos/source/Cosmos.VS.DebugEngine/AD7.Impl/AD7StackFrame.cs
2017-12-22 02:03:32 +00:00

591 lines
21 KiB
C#

using System;
using System.Linq;
using Cosmos.VS.DebugEngine.AD7.Definitions;
using Cosmos.VS.DebugEngine.Engine.Impl;
using Microsoft.VisualStudio.Debugger.Interop;
using IL2CPU.Debug.Symbols;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell;
namespace Cosmos.VS.DebugEngine.AD7.Impl
{
// Represents a logical stack frame on the thread stack.
// Also implements the IDebugExpressionContext interface, which allows expression evaluation and watch windows.
public class AD7StackFrame : IDebugStackFrame2, IDebugExpressionContext2
{
readonly AD7Engine mEngine;
readonly AD7Thread mThread;
//readonly X86ThreadContext m_threadContext;
private string mDocName;
private string mFunctionName;
private uint mLineNum;
private bool mHasSource;
// Must have empty holders, some code looks at length and can run
// before we set them.
internal LOCAL_ARGUMENT_INFO[] mLocalInfos = new LOCAL_ARGUMENT_INFO[] { };
internal LOCAL_ARGUMENT_INFO[] mArgumentInfos = new LOCAL_ARGUMENT_INFO[] { };
// An array of this frame's parameters
private DebugLocalInfo[] mParams;
// An array of this frame's locals
private DebugLocalInfo[] mLocals;
private AD7Process mProcess;
public AD7StackFrame(AD7Engine aEngine, AD7Thread aThread, AD7Process aProcess)
{
mEngine = aEngine;
mThread = aThread;
mProcess = aProcess;
var xProcess = mEngine.mProcess;
if (mHasSource = xProcess.mCurrentAddress.HasValue)
{
UInt32 xAddress = xProcess.mCurrentAddress.Value;
var xSourceInfos = xProcess.mDebugInfoDb.GetSourceInfos(xAddress);
if (!xSourceInfos.ContainsKey(xAddress))
{
//Attempt to find the ASM address of the first ASM line of the C# line that contains
//the current ASM address line
// Because of Asm breakpoints the address we have might be in the middle of a C# line.
// So we find the closest address to ours that is less or equal to ours.
var xQry = from x in xSourceInfos
where x.Key <= xAddress
orderby x.Key descending
select x.Key;
if (xQry.Count() > 0)
{
xAddress = xQry.First();
}
}
if (mHasSource = xSourceInfos.ContainsKey(xAddress))
{
var xSourceInfo = xSourceInfos[xAddress];
mDocName = xSourceInfo.SourceFile;
mFunctionName = xSourceInfo.MethodName;
mLineNum = (uint)xSourceInfo.LineStart;
// Multiple labels that point to a single address can happen because of exception handling exits etc.
// Because of this given an address, we might find more than one label that matches the address.
// Currently, the label we are looking for will always be the first one so we choose that one.
// In the future this might "break", so be careful about this. In the future we may need to classify
// labels in the output and mark them somehow.
var xLabelsForAddr = xProcess.mDebugInfoDb.GetLabels(xAddress);
if (xLabelsForAddr.Length > 0)
{
MethodIlOp xSymbolInfo;
string xLabel = xLabelsForAddr[0]; // Necessary for LINQ
xSymbolInfo = aProcess.mDebugInfoDb.TryGetFirstMethodIlOpByLabelName(xLabel);
if (xSymbolInfo != null)
{
var xMethod = mProcess.mDebugInfoDb.GetMethod(xSymbolInfo.MethodID.Value);
var xAllInfos = aProcess.mDebugInfoDb.GetAllLocalsAndArgumentsInfosByMethodLabelName(xMethod.LabelCall);
mLocalInfos = xAllInfos.Where(q => !q.IsArgument).ToArray();
mArgumentInfos = xAllInfos.Where(q => q.IsArgument).ToArray();
if (mArgumentInfos.Length > 0)
{
mParams = new DebugLocalInfo[mArgumentInfos.Length];
for (int i = 0; i < mArgumentInfos.Length; i++)
{
mParams[i] = new DebugLocalInfo
{
Name = mArgumentInfos[i].NAME,
Index = i,
IsLocal = false
};
}
mParams = mParams.OrderBy(i => i.Name, StringComparer.OrdinalIgnoreCase).ToArray();
}
if (mLocalInfos.Length > 0)
{
mLocals = new DebugLocalInfo[mLocalInfos.Length];
for (int i = 0; i < mLocalInfos.Length; i++)
{
mLocals[i] = new DebugLocalInfo
{
Name = mLocalInfos[i].NAME,
Index = i,
IsLocal = true
};
}
mLocals = mLocals.OrderBy(i => i.Name, StringComparer.OrdinalIgnoreCase).ToArray();
}
}
}
else
{
AD7Util.MessageBox("No Symbol found for address 0x" + xAddress.ToString("X8").ToUpper());
}
xProcess.DebugMsg(String.Format("StackFrame: Returning: {0}#{1}[{2}]", mDocName, mFunctionName, mLineNum));
}
}
if (!mHasSource)
{
xProcess.DebugMsg("StackFrame: No Source available");
}
// If source information is available, create the collections of locals and parameters and populate them with
// values from the debuggee.
//if (m_hasSource) {
//if (mArgumentInfos.Length > 0) {
//m_parameters = new VariableInformation[m_numParameters];
//m_engine.DebuggedProcess.GetFunctionArgumentsByIP(m_threadContext.eip, m_threadContext.ebp, m_parameters);
//}
//if (mLocalInfos.Length > 0) {
//m_locals = new VariableInformation[m_numLocals];
//m_engine.DebuggedProcess.GetFunctionLocalsByIP(m_threadContext.eip, m_threadContext.ebp, m_locals);
//}
//}
}
#region Non-interface methods
// Construct a FRAMEINFO for this stack frame with the requested information.
public void SetFrameInfo(enum_FRAMEINFO_FLAGS dwFieldSpec, out FRAMEINFO frameInfo)
{
System.Diagnostics.Debug.WriteLine("In AD7StackFrame.SetFrameInfo");
System.Diagnostics.Debug.WriteLine("\tdwFieldSpec = " + dwFieldSpec.ToString());
frameInfo = new FRAMEINFO();
//uint ip = m_threadContext.eip;
//DebuggedModule module = null;// m_engine.DebuggedProcess.ResolveAddress(ip);
// The debugger is asking for the formatted name of the function which is displayed in the callstack window.
// There are several optional parts to this name including the module, argument types and values, and line numbers.
// The optional information is requested by setting flags in the dwFieldSpec parameter.
if (dwFieldSpec.HasFlag(enum_FRAMEINFO_FLAGS.FIF_FUNCNAME))
{
// If there is source information, construct a string that contains the module name, function name, and optionally argument names and values.
if (mHasSource)
{
frameInfo.m_bstrFuncName = "";
if (dwFieldSpec.HasFlag(enum_FRAMEINFO_FLAGS.FIF_FUNCNAME_MODULE))
{
// m_
//frameInfo.m_bstrFuncName = System.IO.Path.GetFileName(module.Name) + "!";
frameInfo.m_bstrFuncName = "module!";
}
frameInfo.m_bstrFuncName += mFunctionName;
if (dwFieldSpec.HasFlag(enum_FRAMEINFO_FLAGS.FIF_FUNCNAME_ARGS) && mArgumentInfos.Length > 0)
{
frameInfo.m_bstrFuncName += "(";
for (int i = 0; i < mParams.Length; i++)
{
if ((dwFieldSpec & enum_FRAMEINFO_FLAGS.FIF_FUNCNAME_ARGS_TYPES) != 0)
{
//frameInfo.m_bstrFuncName += m_parameters[i]. + " ";
frameInfo.m_bstrFuncName += "ParamType ";
}
if ((dwFieldSpec & enum_FRAMEINFO_FLAGS.FIF_FUNCNAME_ARGS_NAMES) != 0)
{
frameInfo.m_bstrFuncName += mParams[i].Name;
}
// if ((dwFieldSpec & (uint)enum_FRAMEINFO_FLAGS.FIF_FUNCNAME_ARGS_VALUES) != 0) {
// frameInfo.m_bstrFuncName += "=" + m_parameters[i].m_value;
// }
if (i < mParams.Length - 1)
{
frameInfo.m_bstrFuncName += ", ";
}
}
frameInfo.m_bstrFuncName += ")";
}
if (dwFieldSpec.HasFlag(enum_FRAMEINFO_FLAGS.FIF_FUNCNAME_LINES))
{
frameInfo.m_bstrFuncName += " Line:" + mLineNum.ToString();
}
}
else
{
// No source information, so only return the module name and the instruction pointer.
if (dwFieldSpec.HasFlag(enum_FRAMEINFO_FLAGS.FIF_FUNCNAME_MODULE))
{
//frameInfo.m_bstrFuncName = EngineUtils.GetAddressDescription(module, ip);
}
else
{
//frameInfo.m_bstrFuncName = EngineUtils.GetAddressDescription(null, ip);
}
}
frameInfo.m_dwValidFields |= enum_FRAMEINFO_FLAGS.FIF_FUNCNAME;
}
// The debugger is requesting the name of the module for this stack frame.
if (dwFieldSpec.HasFlag(enum_FRAMEINFO_FLAGS.FIF_MODULE))
{
frameInfo.m_bstrModule = "module";
frameInfo.m_dwValidFields |= enum_FRAMEINFO_FLAGS.FIF_MODULE;
}
// The debugger is requesting the range of memory addresses for this frame.
// For the sample engine, this is the contents of the frame pointer.
if (dwFieldSpec.HasFlag(enum_FRAMEINFO_FLAGS.FIF_STACKRANGE))
{
//frameInfo.m_addrMin = m_threadContext.ebp;
//frameInfo.m_addrMax = m_threadContext.ebp;
frameInfo.m_dwValidFields |= enum_FRAMEINFO_FLAGS.FIF_STACKRANGE;
}
// The debugger is requesting the IDebugStackFrame2 value for this frame info.
if (dwFieldSpec.HasFlag(enum_FRAMEINFO_FLAGS.FIF_FRAME))
{
frameInfo.m_pFrame = this;
frameInfo.m_dwValidFields |= enum_FRAMEINFO_FLAGS.FIF_FRAME;
}
// Does this stack frame of symbols loaded?
if (dwFieldSpec.HasFlag(enum_FRAMEINFO_FLAGS.FIF_DEBUGINFO))
{
frameInfo.m_fHasDebugInfo = mHasSource ? 1 : 0;
frameInfo.m_dwValidFields |= enum_FRAMEINFO_FLAGS.FIF_DEBUGINFO;
}
// Is this frame stale?
if (dwFieldSpec.HasFlag(enum_FRAMEINFO_FLAGS.FIF_STALECODE))
{
frameInfo.m_fStaleCode = 0;
frameInfo.m_dwValidFields |= enum_FRAMEINFO_FLAGS.FIF_STALECODE;
}
// The debugger would like a pointer to the IDebugModule2 that contains this stack frame.
if (dwFieldSpec.HasFlag(enum_FRAMEINFO_FLAGS.FIF_DEBUG_MODULEP))
{
if (mEngine.mModule != null)
{
frameInfo.m_pModule = mEngine.mModule;
frameInfo.m_dwValidFields |= enum_FRAMEINFO_FLAGS.FIF_DEBUG_MODULEP;
}
}
}
// Construct an instance of IEnumDebugPropertyInfo2 for the combined locals and parameters.
private void CreateLocalsPlusArgsProperties(out uint elementsReturned, out IEnumDebugPropertyInfo2 enumObject)
{
elementsReturned = 0;
int localsLength = 0;
if (mLocals != null)
{
localsLength = mLocals.Length;
elementsReturned += (uint)localsLength;
}
if (mParams != null)
{
elementsReturned += (uint)mParams.Length;
}
var propInfo = new DEBUG_PROPERTY_INFO[elementsReturned];
if (mLocals != null)
{
for (int i = 0; i < mLocals.Length; i++)
{
AD7Property property = new AD7Property(mLocals[i], this.mProcess, this);
propInfo[i] = property.ConstructDebugPropertyInfo(enum_DEBUGPROP_INFO_FLAGS.DEBUGPROP_INFO_STANDARD);
}
}
if (mParams != null)
{
for (int i = 0; i < mParams.Length; i++)
{
AD7Property property = new AD7Property(mParams[i], this.mProcess, this);
propInfo[localsLength + i] = property.ConstructDebugPropertyInfo(enum_DEBUGPROP_INFO_FLAGS.DEBUGPROP_INFO_STANDARD);
}
}
propInfo = propInfo.OrderBy(i => i.bstrName).ToArray();
enumObject = new AD7PropertyInfoEnum(propInfo);
}
// Construct an instance of IEnumDebugPropertyInfo2 for the locals collection only.
private void CreateLocalProperties(out uint elementsReturned, out IEnumDebugPropertyInfo2 enumObject)
{
elementsReturned = (uint)mLocals.Length;
var propInfo = new DEBUG_PROPERTY_INFO[mLocals.Length];
for (int i = 0; i < propInfo.Length; i++)
{
AD7Property property = new AD7Property(mLocals[i], mProcess, this);
propInfo[i] = property.ConstructDebugPropertyInfo(enum_DEBUGPROP_INFO_FLAGS.DEBUGPROP_INFO_STANDARD);
}
propInfo = propInfo.OrderBy(i => i.bstrName).ToArray();
enumObject = new AD7PropertyInfoEnum(propInfo);
}
// Construct an instance of IEnumDebugPropertyInfo2 for the parameters collection only.
private void CreateParameterProperties(out uint elementsReturned, out IEnumDebugPropertyInfo2 enumObject)
{
elementsReturned = (uint)mParams.Length;
var propInfo = new DEBUG_PROPERTY_INFO[mParams.Length];
for (int i = 0; i < propInfo.Length; i++)
{
AD7Property property = new AD7Property(mParams[i], mProcess, this);
propInfo[i] = property.ConstructDebugPropertyInfo(enum_DEBUGPROP_INFO_FLAGS.DEBUGPROP_INFO_STANDARD);
}
propInfo = propInfo.OrderBy(i => i.bstrName).ToArray();
enumObject = new AD7PropertyInfoEnum(propInfo);
}
#endregion
#region IDebugStackFrame2 Members
// Creates an enumerator for properties associated with the stack frame, such as local variables.
// The sample engine only supports returning locals and parameters. Other possible values include
// class fields (this pointer), registers, exceptions...
int IDebugStackFrame2.EnumProperties(enum_DEBUGPROP_INFO_FLAGS dwFields, uint nRadix, ref Guid guidFilter, uint dwTimeout, out uint elementsReturned, out IEnumDebugPropertyInfo2 enumObject)
{
int hr;
elementsReturned = 0;
enumObject = null;
try
{
if (guidFilter == AD7Guids.guidFilterLocalsPlusArgs ||
guidFilter == AD7Guids.guidFilterAllLocalsPlusArgs)
{
CreateLocalsPlusArgsProperties(out elementsReturned, out enumObject);
hr = VSConstants.S_OK;
}
else if (guidFilter == AD7Guids.guidFilterLocals)
{
CreateLocalProperties(out elementsReturned, out enumObject);
hr = VSConstants.S_OK;
}
else if (guidFilter == AD7Guids.guidFilterArgs)
{
CreateParameterProperties(out elementsReturned, out enumObject);
hr = VSConstants.S_OK;
}
else
{
hr = VSConstants.E_NOTIMPL;
}
}
//catch (ComponentException e)
//{
// return e.HResult;
//}
catch (Exception e)
{
return EngineUtils.UnexpectedException(e);
}
return hr;
}
// Gets the code context for this stack frame. The code context represents the current instruction pointer in this stack frame.
int IDebugStackFrame2.GetCodeContext(out IDebugCodeContext2 memoryAddress)
{
memoryAddress = null;
try
{
//memoryAddress = new AD7MemoryAddress(m_engine, m_threadContext.eip);
return VSConstants.E_NOTIMPL;
}
//catch (ComponentException e)
//{
// return e.HResult;
//}
catch (Exception e)
{
return EngineUtils.UnexpectedException(e);
}
}
// Gets a description of the properties of a stack frame.
// Calling the IDebugProperty2::EnumChildren method with appropriate filters can retrieve the local variables, method parameters, registers, and "this"
// pointer associated with the stack frame. The debugger calls EnumProperties to obtain these values in the sample.
int IDebugStackFrame2.GetDebugProperty(out IDebugProperty2 property)
{
throw new NotImplementedException();
}
// Gets the document context for this stack frame. The debugger will call this when the current stack frame is changed
// and will use it to open the correct source document for this stack frame.
int IDebugStackFrame2.GetDocumentContext(out IDebugDocumentContext2 docContext)
{
docContext = null;
try
{
if (mHasSource)
{
// Assume all lines begin and end at the beginning of the line.
TEXT_POSITION begTp = new TEXT_POSITION();
begTp.dwColumn = 0;
begTp.dwLine = mLineNum - 1;
TEXT_POSITION endTp = new TEXT_POSITION();
endTp.dwColumn = 0;
endTp.dwLine = mLineNum - 1;
docContext = new AD7DocumentContext(mDocName, begTp, endTp, null);
return VSConstants.S_OK;
}
}
//catch (ComponentException e)
//{
// return e.HResult;
//}
catch (Exception e)
{
return EngineUtils.UnexpectedException(e);
}
return VSConstants.S_FALSE;
}
// Gets an evaluation context for expression evaluation within the current context of a stack frame and thread.
// Generally, an expression evaluation context can be thought of as a scope for performing expression evaluation.
// Call the IDebugExpressionContext2::ParseText method to parse an expression and then call the resulting IDebugExpression2::EvaluateSync
// or IDebugExpression2::EvaluateAsync methods to evaluate the parsed expression.
int IDebugStackFrame2.GetExpressionContext(out IDebugExpressionContext2 ppExprCxt)
{
ppExprCxt = (IDebugExpressionContext2)this;
return VSConstants.S_OK;
}
// Gets a description of the stack frame.
int IDebugStackFrame2.GetInfo(enum_FRAMEINFO_FLAGS dwFieldSpec, uint nRadix, FRAMEINFO[] pFrameInfo)
{
try
{
SetFrameInfo((enum_FRAMEINFO_FLAGS)dwFieldSpec, out pFrameInfo[0]);
return VSConstants.S_OK;
}
//catch (ComponentException e)
//{
// return e.HResult;
//}
catch (Exception e)
{
return EngineUtils.UnexpectedException(e);
}
}
// Gets the language associated with this stack frame.
// In this sample, all the supported stack frames are C++
int IDebugStackFrame2.GetLanguageInfo(ref string pbstrLanguage, ref Guid pguidLanguage)
{
pbstrLanguage = "CSharp";
pguidLanguage = AD7Guids.guidLanguageCSharp;
return VSConstants.S_OK;
}
// Gets the name of the stack frame.
// The name of a stack frame is typically the name of the method being executed.
int IDebugStackFrame2.GetName(out string name)
{
name = null;
try
{
name = mFunctionName;
return VSConstants.S_OK;
}
//catch (ComponentException e)
//{
// return e.HResult;
//}
catch (Exception e)
{
return EngineUtils.UnexpectedException(e);
}
}
// Gets a machine-dependent representation of the range of physical addresses associated with a stack frame.
int IDebugStackFrame2.GetPhysicalStackRange(out ulong addrMin, out ulong addrMax)
{
addrMin = 0;// m_threadContext.ebp;
addrMax = 0;// m_threadContext.ebp;
return VSConstants.S_OK;
}
// Gets the thread associated with a stack frame.
int IDebugStackFrame2.GetThread(out IDebugThread2 thread)
{
thread = mThread;
return VSConstants.S_OK;
}
#endregion
// Retrieves the name of the evaluation context.
// The name is the description of this evaluation context. It is typically something that can be parsed by an expression evaluator
// that refers to this exact evaluation context. For example, in C++ the name is as follows:
// "{ function-name, source-file-name, module-file-name }"
int IDebugExpressionContext2.GetName(out string pbstrName)
{
throw new NotImplementedException();
}
// Parses a text-based expression for evaluation.
// The engine sample only supports locals and parameters so the only task here is to check the names in those collections.
int IDebugExpressionContext2.ParseText(string pszCode, enum_PARSEFLAGS dwFlags, uint nRadix, out IDebugExpression2 ppExpr,
out string pbstrError,
out uint pichError)
{
//System.Windows.Forms.AD7Util.MessageBox("pszCode: " + pszCode);
pbstrError = "";
pichError = 0;
ppExpr = null;
try
{
if (mParams != null)
{
foreach (DebugLocalInfo currVariable in mParams)
{
if (String.CompareOrdinal(currVariable.Name, pszCode) == 0)
{
ppExpr = new AD7Expression(currVariable, mProcess, this);
return VSConstants.S_OK;
}
}
}
if (mLocals != null)
{
foreach (DebugLocalInfo currVariable in mLocals)
{
if (String.CompareOrdinal(currVariable.Name, pszCode) == 0)
{
ppExpr = new AD7Expression(currVariable, mProcess, this);
return VSConstants.S_OK;
}
}
}
pbstrError = "Invalid Expression";
pichError = (uint)pbstrError.Length;
return VSConstants.S_FALSE;
}
catch (Exception e)
{
return EngineUtils.UnexpectedException(e);
}
}
}
}