using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Debugger.Interop; using System.Diagnostics; using Cosmos.Debug.Common; using System.Windows.Forms; using Dapper; using DapperExtensions; using SQLinq; using SQLinq.Dapper; namespace Cosmos.Debug.VSDebugEngine { // 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.Line; // 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 { MessageBox.Show("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.MessageBox.Show("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); } } } }