diff options
author | dotnet-bot <dotnet-bot@microsoft.com> | 2015-12-01 06:54:09 +0300 |
---|---|---|
committer | Alex Ghiondea <ghiondea.alexandru@microsoft.com> | 2015-12-01 20:44:51 +0300 |
commit | 9d362c1b211974aff8eb7a411d371713b48b2e5c (patch) | |
tree | 0e58dfbc6549827b50c9dd355688cca914a4776e /src/System.Private.StackTraceGenerator | |
parent | 8199af35a5a5bbf932417d6b9c59809ced819bfd (diff) |
Port StackTraceGenerator to CoreRT
[tfs-changeset: 1552759]
Diffstat (limited to 'src/System.Private.StackTraceGenerator')
10 files changed, 1222 insertions, 0 deletions
diff --git a/src/System.Private.StackTraceGenerator/src/Internal/Dia/DiaEnums.cs b/src/System.Private.StackTraceGenerator/src/Internal/Dia/DiaEnums.cs new file mode 100644 index 000000000..67bc5af7f --- /dev/null +++ b/src/System.Private.StackTraceGenerator/src/Internal/Dia/DiaEnums.cs @@ -0,0 +1,106 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using global::System; +using global::System.Diagnostics; + +namespace Internal.StackGenerator.Dia +{ + internal enum SymTagEnum + { + SymTagNull, + SymTagExe, + SymTagCompiland, + SymTagCompilandDetails, + SymTagCompilandEnv, + SymTagFunction, + SymTagBlock, + SymTagData, + SymTagAnnotation, + SymTagLabel, + SymTagPublicSymbol, + SymTagUDT, + SymTagEnum, + SymTagFunctionType, + SymTagPointerType, + SymTagArrayType, + SymTagBaseType, + SymTagTypedef, + SymTagBaseClass, + SymTagFriend, + SymTagFunctionArgType, + SymTagFuncDebugStart, + SymTagFuncDebugEnd, + SymTagUsingNamespace, + SymTagVTableShape, + SymTagVTable, + SymTagCustom, + SymTagThunk, + SymTagCustomType, + SymTagManagedType, + SymTagDimension, + SymTagCallSite, + SymTagInlineSite, + SymTagBaseInterface, + SymTagVectorType, + SymTagMatrixType, + SymTagHLSLType, + SymTagMax + } + + internal enum NameSearchOptions + { + nsNone, + nsfCaseSensitive = 0x1, + nsfCaseInsensitive = 0x2, + nsfFNameExt = 0x4, + nsfRegularExpression = 0x8, + nsfUndecoratedName = 0x10, + + // For backward compabibility: + nsCaseSensitive = nsfCaseSensitive, + nsCaseInsensitive = nsfCaseInsensitive, + nsFNameExt = nsfFNameExt, + nsRegularExpression = nsfRegularExpression | nsfCaseSensitive, + nsCaseInRegularExpression = nsfRegularExpression | nsfCaseInsensitive + } + + internal enum BasicType + { + btNoType = 0, + btVoid = 1, + btChar = 2, + btWChar = 3, + btInt = 6, + btUInt = 7, + btFloat = 8, + btBCD = 9, + btBool = 10, + btLong = 13, + btULong = 14, + btCurrency = 25, + btDate = 26, + btVariant = 27, + btComplex = 28, + btBit = 29, + btBSTR = 30, + btHresult = 31, + + btMAX = 0xffff + } + + internal enum DataKind + { + DataIsUnknown, + DataIsLocal, + DataIsStaticLocal, + DataIsParam, + DataIsObjectPtr, + DataIsFileStatic, + DataIsGlobal, + DataIsMember, + DataIsStaticMember, + DataIsConstant + } +} + diff --git a/src/System.Private.StackTraceGenerator/src/Internal/Dia/DiaInterfaces.cs b/src/System.Private.StackTraceGenerator/src/Internal/Dia/DiaInterfaces.cs new file mode 100644 index 000000000..abf8c9c31 --- /dev/null +++ b/src/System.Private.StackTraceGenerator/src/Internal/Dia/DiaInterfaces.cs @@ -0,0 +1,282 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using global::System; +using global::System.Diagnostics; +using global::Internal.LightweightInterop; + +namespace Internal.StackGenerator.Dia +{ + internal sealed class IDiaDataSource : ComInterface + { + public IDiaDataSource(IntPtr punk) + : base(punk) + { + } + + public int LoadDataFromPdb(String pdbPath) + { + unsafe + { + fixed (char* _pdbPath = pdbPath) + { + int hr = S.StdCall<int>(GetVTableMember(4), Punk, _pdbPath); + GC.KeepAlive(this); + return hr; + } + } + } + + public int OpenSession(out IDiaSession session) + { + session = null; + IntPtr _session; + int hr = S.StdCall<int>(GetVTableMember(8), Punk, out _session); + GC.KeepAlive(this); + if (hr != S_OK) + return hr; + session = new IDiaSession(_session); + return hr; + } + } + + internal sealed class IDiaSession : ComInterface + { + public IDiaSession(IntPtr punk) + : base(punk) + { + } + + public int FindChildren(IDiaSymbol parent, SymTagEnum symTag, String name, NameSearchOptions compareFlags, out IDiaEnumSymbols enumSymbols) + { + enumSymbols = null; + IntPtr _enumSymbols; + int hr; + unsafe + { + fixed (char* _name = name) + { + hr = S.StdCall<int>(GetVTableMember(8), Punk, parent.Punk, (int)symTag, _name, (int)compareFlags, out _enumSymbols); + } + } + GC.KeepAlive(this); + GC.KeepAlive(parent); + if (hr != S_OK) + return hr; + enumSymbols = new IDiaEnumSymbols(_enumSymbols); + return hr; + } + + + public int FindSymbolByRVA(int rva, SymTagEnum symTag, out IDiaSymbol symbol) + { + symbol = null; + IntPtr _symbol; + int hr = S.StdCall<int>(GetVTableMember(14), Punk, rva, (int)symTag, out _symbol); + GC.KeepAlive(this); + if (hr != S_OK) + return hr; + symbol = new IDiaSymbol(_symbol); + return hr; + } + + public int FindLinesByRVA(int rva, int length, out IDiaEnumLineNumbers enumLineNumbers) + { + enumLineNumbers = null; + IntPtr _enumLineNumbers; + int hr = S.StdCall<int>(GetVTableMember(25), Punk, rva, length, out _enumLineNumbers); + GC.KeepAlive(this); + if (hr != S_OK) + return hr; + enumLineNumbers = new IDiaEnumLineNumbers(_enumLineNumbers); + return hr; + } + } + + internal sealed class IDiaEnumSymbols : ComInterface + { + public IDiaEnumSymbols(IntPtr punk) + : base(punk) + { + } + + public int Count(out int count) + { + int hr = S.StdCall<int>(GetVTableMember(4), Punk, out count); + GC.KeepAlive(this); + return hr; + } + + public int Item(int index, out IDiaSymbol symbol) + { + symbol = null; + IntPtr pSymbol; + int hr = S.StdCall<int>(GetVTableMember(5), Punk, index, out pSymbol); + GC.KeepAlive(this); + if (hr != S_OK) + return hr; + symbol = new IDiaSymbol(pSymbol); + return hr; + } + } + + internal sealed class IDiaSymbol : ComInterface + { + public IDiaSymbol(IntPtr punk) + : base(punk) + { + } + + public int GetSymTag(out SymTagEnum symTagEnum) + { + symTagEnum = default(SymTagEnum); + int _symTagEnum; + int hr = S.StdCall<int>(GetVTableMember(4), Punk, out _symTagEnum); + GC.KeepAlive(this); + symTagEnum = (SymTagEnum)_symTagEnum; + return hr; + } + + public int GetName(out String name) + { + name = null; + IntPtr _name; + int hr = S.StdCall<int>(GetVTableMember(5), Punk, out _name); + GC.KeepAlive(this); + if (hr != S_OK) + return hr; + name = _name.MarshalBstr(); + return hr; + } + + public int GetType(out IDiaSymbol symbol) + { + symbol = null; + IntPtr _symbol; + int hr = S.StdCall<int>(GetVTableMember(8), Punk, out _symbol); + GC.KeepAlive(this); + if (hr != S_OK) + return hr; + symbol = new IDiaSymbol(_symbol); + return hr; + } + + public int GetDataKind(out DataKind dataKind) + { + dataKind = default(DataKind); + int _dataKindEnum; + int hr = S.StdCall<int>(GetVTableMember(9), Punk, out _dataKindEnum); + GC.KeepAlive(this); + dataKind = (DataKind)_dataKindEnum; + return hr; + } + + public int GetReference(out bool isReference) + { + isReference = false; + int _isReference; + int hr = S.StdCall<int>(GetVTableMember(48), Punk, out _isReference); + GC.KeepAlive(this); + isReference = (_isReference != 0); + return hr; + } + + public int GetBaseType(out BasicType baseType) + { + baseType = default(BasicType); + int _baseType; + int hr = S.StdCall<int>(GetVTableMember(43), Punk, out _baseType); + GC.KeepAlive(this); + baseType = (BasicType)_baseType; + return hr; + } + + public int GetLength(out long length) + { + int hr = S.StdCall<int>(GetVTableMember(17), Punk, out length); + GC.KeepAlive(this); + return hr; + } + } + + internal sealed class IDiaEnumLineNumbers : ComInterface + { + public IDiaEnumLineNumbers(IntPtr punk) + : base(punk) + { + } + + public int Count(out int count) + { + int hr = S.StdCall<int>(GetVTableMember(4), Punk, out count); + GC.KeepAlive(this); + return hr; + } + + public int Item(int index, out IDiaLineNumber lineNumber) + { + lineNumber = null; + IntPtr pLineNumber; + int hr = S.StdCall<int>(GetVTableMember(5), Punk, index, out pLineNumber); + GC.KeepAlive(this); + if (hr != S_OK) + return hr; + lineNumber = new IDiaLineNumber(pLineNumber); + return hr; + } + } + + internal sealed class IDiaLineNumber : ComInterface + { + public IDiaLineNumber(IntPtr punk) + : base(punk) + { + } + + public int SourceFile(out IDiaSourceFile sourceFile) + { + sourceFile = null; + IntPtr _sourceFile; + int hr = S.StdCall<int>(GetVTableMember(4), Punk, out _sourceFile); + GC.KeepAlive(this); + if (hr != S_OK) + return hr; + sourceFile = new IDiaSourceFile(_sourceFile); + return hr; + } + + public int LineNumber(out int lineNumber) + { + int hr = S.StdCall<int>(GetVTableMember(5), Punk, out lineNumber); + GC.KeepAlive(this); + return hr; + } + + public int ColumnNumber(out int columnNumber) + { + int hr = S.StdCall<int>(GetVTableMember(7), Punk, out columnNumber); + GC.KeepAlive(this); + return hr; + } + } + + internal sealed class IDiaSourceFile : ComInterface + { + public IDiaSourceFile(IntPtr punk) + : base(punk) + { + } + + public int FileName(out String fileName) + { + fileName = null; + IntPtr _fileName; + int hr = S.StdCall<int>(GetVTableMember(4), Punk, out _fileName); + GC.KeepAlive(this); + if (hr != S_OK) + return hr; + fileName = _fileName.MarshalBstr(); + return hr; + } + } +} diff --git a/src/System.Private.StackTraceGenerator/src/Internal/Dia/Guids.cs b/src/System.Private.StackTraceGenerator/src/Internal/Dia/Guids.cs new file mode 100644 index 000000000..21fb5d7fa --- /dev/null +++ b/src/System.Private.StackTraceGenerator/src/Internal/Dia/Guids.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using global::System; +using global::System.Diagnostics; +using global::System.Collections.Generic; + +namespace Internal.StackGenerator.Dia +{ + internal static class Guids + { + public static readonly IEnumerable<Guid> DiaSource_CLSIDs = + new Guid[] + { + new Guid("3BFCEA48-620F-4B6B-81F7-B9AF75454C7D"), // msdia120.dll + new Guid("761D3BCD-1304-41D5-94E8-EAC54E4AC172"), // msdia110.dll + }; + + public static readonly Guid IID_IDiaDataSource = new Guid("79F1BB5F-B66E-48E5-B6A9-1545C323CA3D"); + } +} + diff --git a/src/System.Private.StackTraceGenerator/src/Internal/Dia/StdCall.cs b/src/System.Private.StackTraceGenerator/src/Internal/Dia/StdCall.cs new file mode 100644 index 000000000..6c2dc1e64 --- /dev/null +++ b/src/System.Private.StackTraceGenerator/src/Internal/Dia/StdCall.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using global::System; +using global::System.Diagnostics; +using global::System.Runtime.InteropServices; + +namespace Internal.StackGenerator.Dia +{ + [McgIntrinsics] + internal static class S + { + public static unsafe T StdCall<T>(IntPtr pMethod, IntPtr pThis) { throw NotImplemented.ByDesign; } + public static unsafe T StdCall<T>(IntPtr pMethod, IntPtr pThis, char* pc) { throw NotImplemented.ByDesign; } + public static unsafe T StdCall<T>(IntPtr pMethod, IntPtr pThis, out IntPtr ppOut) { throw NotImplemented.ByDesign; } + public static unsafe T StdCall<T>(IntPtr pMethod, IntPtr pThis, out long ppOut) { throw NotImplemented.ByDesign; } + public static unsafe T StdCall<T>(IntPtr pMethod, IntPtr pThis, out int pOut) { throw NotImplemented.ByDesign; } + public static unsafe T StdCall<T>(IntPtr pMethod, IntPtr pThis, int i, out IntPtr ppOut) { throw NotImplemented.ByDesign; } + public static unsafe T StdCall<T>(IntPtr pMethod, IntPtr pThis, int i, int j, out IntPtr ppOut) { throw NotImplemented.ByDesign; } + public static unsafe T StdCall<T>(IntPtr pMethod, IntPtr pThis, IntPtr parent, int symTag, char* name, int compareFlags, out IntPtr ppResult) { throw NotImplemented.ByDesign; } + } +} + + + diff --git a/src/System.Private.StackTraceGenerator/src/Internal/LightweightInterop/ComInterface.cs b/src/System.Private.StackTraceGenerator/src/Internal/LightweightInterop/ComInterface.cs new file mode 100644 index 000000000..e0f1d2982 --- /dev/null +++ b/src/System.Private.StackTraceGenerator/src/Internal/LightweightInterop/ComInterface.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using global::System; +using global::System.Diagnostics; + +namespace Internal.LightweightInterop +{ + // + // Lightweight managed wrapper for a COM interface pointer. This is used for the very limited purpose of accessing Dia to generate decent stack traces. + // Thus, the support is intentionally minimal: + // + // - Each ComIfc wraps a single COM interface pointer and owns one ref-count: there's no attempt to make COM identity match managed object identity. + // + internal unsafe abstract class ComInterface + { + public const int S_OK = 0; + + protected ComInterface(IntPtr punk) + { + Punk = punk; + } + + public IntPtr Punk { get; private set; } // Unmanaged COM interface pointer. + + protected IntPtr GetVTableMember(int index) + { + unsafe + { + IntPtr* pVTable = *((IntPtr**)Punk); + IntPtr member = pVTable[index]; + return member; + } + } + + private void Release() + { + IntPtr punk = Punk; + Punk = (IntPtr)0; + if (punk != (IntPtr)0) + { + IntPtr* pVTable = *((IntPtr**)punk); + IntPtr releaseMember = pVTable[2]; + S.StdCall<uint>(releaseMember, punk); + } + } + + ~ComInterface() + { + this.Release(); + } + } +} diff --git a/src/System.Private.StackTraceGenerator/src/Internal/LightweightInterop/MarshalExtensions.cs b/src/System.Private.StackTraceGenerator/src/Internal/LightweightInterop/MarshalExtensions.cs new file mode 100644 index 000000000..4d2b7030c --- /dev/null +++ b/src/System.Private.StackTraceGenerator/src/Internal/LightweightInterop/MarshalExtensions.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using global::System; +using global::System.Diagnostics; +using global::System.Runtime.InteropServices; + +namespace Internal.LightweightInterop +{ + internal static class MarshalExtensions + { + public static String MarshalBstr(this IntPtr bstr) + { + unsafe + { + if (bstr == ((IntPtr)0)) + return null; + try + { + char* pc = (char*)bstr; + int cchLen = 0; + // This marshaler is for stack traces so if a string looks suspiciously long, chop it. + while (pc[cchLen] != '\0' && cchLen < 300) + { + cchLen++; + } + return new String(pc, 0, cchLen); + } + finally + { + SysFreeString(bstr); + } + } + } + + [DllImport("OleAut32")] + private static extern void SysFreeString(IntPtr bstr); + } +} diff --git a/src/System.Private.StackTraceGenerator/src/Internal/LightweightInterop/StdCall.cs b/src/System.Private.StackTraceGenerator/src/Internal/LightweightInterop/StdCall.cs new file mode 100644 index 000000000..7d2f18ddc --- /dev/null +++ b/src/System.Private.StackTraceGenerator/src/Internal/LightweightInterop/StdCall.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using global::System; +using global::System.Diagnostics; +using global::System.Runtime.InteropServices; + +namespace Internal.LightweightInterop +{ + [McgIntrinsics] + internal static class S + { + public static T StdCall<T>(IntPtr pMethod, IntPtr pThis) { throw NotImplemented.ByDesign; } + } +} + diff --git a/src/System.Private.StackTraceGenerator/src/Internal/StackTraceGenerator/Attributes.cs b/src/System.Private.StackTraceGenerator/src/Internal/StackTraceGenerator/Attributes.cs new file mode 100644 index 000000000..30fe725ba --- /dev/null +++ b/src/System.Private.StackTraceGenerator/src/Internal/StackTraceGenerator/Attributes.cs @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.Runtime.InteropServices +{ + internal class McgIntrinsicsAttribute : Attribute + { + } +}
\ No newline at end of file diff --git a/src/System.Private.StackTraceGenerator/src/Internal/StackTraceGenerator/StackTraceGenerator.cs b/src/System.Private.StackTraceGenerator/src/Internal/StackTraceGenerator/StackTraceGenerator.cs new file mode 100644 index 000000000..ab91659d4 --- /dev/null +++ b/src/System.Private.StackTraceGenerator/src/Internal/StackTraceGenerator/StackTraceGenerator.cs @@ -0,0 +1,611 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using global::System; +using global::System.Text; +using global::System.Diagnostics; +using global::System.Runtime.InteropServices; +using global::Internal.Runtime.Augments; +using global::Internal.StackGenerator.Dia; + +namespace Internal.StackTraceGenerator +{ + public static class StackTraceGenerator + { + // + // Makes reasonable effort to construct one useful line of a stack trace. Returns null if it can't. + // + public static String CreateStackTraceString(IntPtr ip, bool includeFileInfo) + { + try + { + int hr; + + int rva; + IDiaSession session = GetDiaSession(ip, out rva); + if (session == null) + return null; + + StringBuilder sb = new StringBuilder(); + IDiaSymbol symbol; + hr = session.FindSymbolByRVA(rva, SymTagEnum.SymTagFunction, out symbol); + if (hr != S_OK) + return null; + String functionName; + hr = symbol.GetName(out functionName); + if (hr == S_OK) + sb.Append(functionName.Demanglify()); + else + sb.Append("<Function Name Not Available>"); + + sb.Append(CreateParameterListString(session, symbol)); + + if (includeFileInfo) + { + sb.Append(CreateSourceInfoString(session, rva)); + } + + return sb.ToString(); + } + catch + { + return null; + } + } + + // + // Makes reasonable effort to get source info. Returns null sourceFile and 0 lineNumber/columnNumber if it can't. + // + public static void TryGetSourceLineInfo(IntPtr ip, out string fileName, out int lineNumber, out int columnNumber) + { + fileName = null; + lineNumber = 0; + columnNumber = 0; + int rva; + IDiaSession session = GetDiaSession(ip, out rva); + if (session == null) + return; + TryGetSourceLineInfo(session, rva, out fileName, out lineNumber, out columnNumber); + } + + // + // Get a IDiaDataSource object + // + private static IDiaDataSource GetDiaDataSource() + { + Guid iid = Guids.IID_IDiaDataSource; + int hr; + foreach (Guid clsId in Guids.DiaSource_CLSIDs) + { + unsafe + { + byte[] _clsid = clsId.ToByteArray(); + byte[] _iid = iid.ToByteArray(); + fixed (byte* pclsid = _clsid) + { + fixed (byte* piid = _iid) + { + IntPtr _dataSource; + hr = CoCreateInstance(pclsid, (IntPtr)0, CLSCTX_INPROC, piid, out _dataSource); + if (hr == S_OK) + return new IDiaDataSource(_dataSource); + } + } + } + } + return null; + } + + // + // Create the method parameter list. + // + private static String CreateParameterListString(IDiaSession session, IDiaSymbol symbol) + { + StringBuilder sb = new StringBuilder("("); + + // find the parameters + IDiaEnumSymbols dataSymbols; + int hr = session.FindChildren(symbol, SymTagEnum.SymTagData, null, NameSearchOptions.nsNone, out dataSymbols); + if (hr == S_OK) + { + int count; + hr = dataSymbols.Count(out count); + if (hr == S_OK) + { + for (int i = 0, iParam = 0; i < count; i++) + { + IDiaSymbol dataSym; + hr = dataSymbols.Item(i, out dataSym); + if (hr != S_OK) + continue; + + DataKind dataKind; + hr = dataSym.GetDataKind(out dataKind); + if (hr != S_OK || dataKind != DataKind.DataIsParam) + continue; + + string paramName; + hr = dataSym.GetName(out paramName); + if (hr != S_OK) + { + continue; + } + + //this approximates the way C# displays methods by not including these hidden arguments + if (paramName == "InstParam" || paramName == "this") + { + continue; + } + + IDiaSymbol parameterType; + hr = dataSym.GetType(out parameterType); + if (hr != S_OK) + { + continue; + } + + if (iParam++ != 0) + sb.Append(", "); + + sb.Append(parameterType.ToTypeString(session)); + sb.Append(' '); + sb.Append(paramName); + } + } + } + sb.Append(')'); + return sb.ToString(); + } + + // + // Retrieve the source fileName, line number, and column + // + private static void TryGetSourceLineInfo(IDiaSession session, int rva, out string fileName, out int lineNumber, out int columnNumber) + { + fileName = null; + lineNumber = 0; + columnNumber = 0; + IDiaEnumLineNumbers lineNumbers; + int hr = session.FindLinesByRVA(rva, 1, out lineNumbers); + if (hr == S_OK) + { + int numLineNumbers; + hr = lineNumbers.Count(out numLineNumbers); + if (hr == S_OK && numLineNumbers > 0) + { + IDiaLineNumber ln; + hr = lineNumbers.Item(0, out ln); + if (hr == S_OK) + { + IDiaSourceFile sourceFile; + hr = ln.SourceFile(out sourceFile); + if (hr == S_OK) + { + hr = sourceFile.FileName(out fileName); + if (hr == S_OK) + { + hr = ln.LineNumber(out lineNumber); + if (hr == S_OK) + { + hr = ln.ColumnNumber(out columnNumber); + } + } + } + } + } + } + } + + // + // Generate the " in <filename>:line <line#>" section. + // + private static String CreateSourceInfoString(IDiaSession session, int rva) + { + StringBuilder sb = new StringBuilder(); + string fileName; + int lineNumber, columnNumber; + TryGetSourceLineInfo(session, rva, out fileName, out lineNumber, out columnNumber); + if(!string.IsNullOrEmpty(fileName)) + { + sb.Append(" in ").Append(fileName); + if(lineNumber >= 0) + { + sb.Append(":line ").Append(lineNumber); + } + } + return sb.ToString(); + } + + // + // Clean up all the "$2_" sweetness that ILMerge contributes and + // replace type-name separator "::" by "." + // + private static String Demanglify(this String s) + { + StringBuilder sb = new StringBuilder(); + int i = 0; + while (i < s.Length) + { + sb.Append(s[i++]); + + if (i == s.Length) + continue; + + if (s[i - 1] == '$' && Char.IsNumber(s[i])) + { + if (i != 1 && (s[i - 2] != ' ' && s[i - 2] != '<')) + continue; + int lookAhead = i + 1; + while (lookAhead < s.Length && Char.IsNumber(s[lookAhead])) + lookAhead++; + if (lookAhead == s.Length || s[lookAhead] != '_') + continue; + sb = sb.Remove(sb.Length - 1, 1); + i = lookAhead + 1; + } + else if (s[i - 1] == ':' && s[i] == ':') + { + sb.Remove(sb.Length - 1, 1); + sb.Append('.'); + i++; + } + } + + return sb.ToString(); + } + + private static String ToTypeString(this IDiaSymbol parameterType, IDiaSession session) + { + bool ignore; + return parameterType.ToTypeStringWorker(session, 0, out ignore); + } + + private static String ToTypeStringWorker(this IDiaSymbol parameterType, IDiaSession session, int recursionLevel, out bool isValueTypeOrByRef) + { + int hr; + isValueTypeOrByRef = false; + + // Block runaway recursions. + if (recursionLevel++ > 10) + return "?"; + + SymTagEnum symTag; + hr = parameterType.GetSymTag(out symTag); + if (hr != S_OK) + return "?"; + if (symTag == SymTagEnum.SymTagPointerType) + { + bool isReference; + hr = parameterType.GetReference(out isReference); + if (hr != S_OK) + return "?"; + + if (isReference) + { + // An isReference pointer can mean one of two things: + // 1. ELEMENT_TYPE_BYREF + // 2. An indication that the UDT that follows is actually a class, not a struct. + // + isValueTypeOrByRef = true; + IDiaSymbol targetType; + hr = parameterType.GetType(out targetType); + if (hr != S_OK) + return "?"; + bool targetIsValueTypeOrByRef; + String targetTypeString = targetType.ToTypeStringWorker(session, recursionLevel, out targetIsValueTypeOrByRef); + if (targetIsValueTypeOrByRef) + return targetTypeString + "&"; + else + return targetTypeString; + } + else + { + // A non-isReference pointer means an ELEMENT_TYPE_PTR + IDiaSymbol targetType; + hr = parameterType.GetType(out targetType); + if (hr != S_OK) + return "?"; + bool ignore; + return targetType.ToTypeStringWorker(session, recursionLevel, out ignore) + "*"; + } + } + else if (symTag == SymTagEnum.SymTagArrayType) + { + // Note: We don't actually hit this case in NUTC-generated PDB's as NUTC emits arrays as if they were UDT's with square brackets in the name. + // But just in case NUTC ever changes its PDB emission, we'll print out the most obvious interpretation and hope we're right. + IDiaSymbol elementType; + hr = parameterType.GetType(out elementType); + if (hr != S_OK) + return "?"; + bool ignore; + return elementType.ToTypeStringWorker(session, recursionLevel, out ignore) + "[]"; + } + else if (symTag == SymTagEnum.SymTagUDT || symTag == SymTagEnum.SymTagEnum) + { + // Need to figure out whether this is a value type as our recursive caller needs to know whether the "byref pointer" that wrapped this + // is a true managed byref or just the "byref pointer" that wraps all non-valuetypes. + if (symTag == SymTagEnum.SymTagEnum) + { + isValueTypeOrByRef = true; + } + else + { + IDiaEnumSymbols baseClasses; + hr = session.FindChildren(parameterType, SymTagEnum.SymTagBaseClass, null, 0, out baseClasses); + if (hr != S_OK) + return "?"; + int count; + hr = baseClasses.Count(out count); + if (hr != S_OK) + return "?"; + for (int i = 0; i < count; i++) + { + IDiaSymbol baseClass; + if (S_OK == baseClasses.Item(i, out baseClass)) + { + String baseClassName; + if (S_OK == baseClass.GetName(out baseClassName)) + { + if (baseClassName == "System::ValueType") + isValueTypeOrByRef = true; + } + } + } + } + + String name; + hr = parameterType.GetName(out name); + if (hr != S_OK) + return "?"; + return name.RemoveNamespaces().Demanglify(); + } + else if (symTag == SymTagEnum.SymTagBaseType) + { + // Certain "primitive" types are encoded specially. + BasicType basicType; + hr = parameterType.GetBaseType(out basicType); + if (hr != S_OK) + return "?"; + long length; + hr = parameterType.GetLength(out length); + if (hr != S_OK) + return "?"; + return ConvertBasicTypeToTypeString(basicType, length, out isValueTypeOrByRef); + } + else + { + return "?"; + } + } + + private static String ConvertBasicTypeToTypeString(BasicType basicType, long length, out bool isValueTypeOrByRef) + { + isValueTypeOrByRef = true; + switch (basicType) + { + case BasicType.btNoType: + return "Unknown"; + + case BasicType.btVoid: + return "Void"; + + case BasicType.btChar: + return "Byte"; + + case BasicType.btWChar: + return "Char"; + + case BasicType.btInt: + if (length != 1L) + { + if (length == 2L) + { + return "Int16"; + } + if ((length != 4L) && (length == 8L)) + { + return "Int64"; + } + return "Int32"; + } + return "SByte"; + + case BasicType.btUInt: + if (length != 1L) + { + if (length == 2L) + { + return "UInt16"; + } + if ((length != 4L) && (length == 8L)) + { + return "UInt64"; + } + return "UInt32"; + } + return "Byte"; + + case BasicType.btFloat: + if (length != 8L) + { + return "Single"; + } + return "Double"; + + case BasicType.btBCD: + return "BCD"; + + case BasicType.btBool: + return "Boolean"; + + case BasicType.btLong: + return "Int64"; + + case BasicType.btULong: + return "UInt64"; + + case BasicType.btCurrency: + return "Currency"; + + case BasicType.btDate: + return "Date"; + + case BasicType.btVariant: + return "Variant"; + + case BasicType.btComplex: + return "Complex"; + + case BasicType.btBit: + return "Bit"; + + case BasicType.btBSTR: + return "BSTR"; + + case BasicType.btHresult: + return "Hresult"; + + default: + return "?"; + } + } + + // + // Attempt to remove namespaces from types. Unfortunately, this isn't straightforward as PDB's present generic instances as "regular types with angle brackets" + // so "s" could be something like "System::Collections::Generics::List$1<System::String>". Worse, the PDB also uses "::" to separate nested types from + // their outer types so these represent collateral damage. And we assume that namespaces (unlike types) have reasonable names (i.e. no names with wierd characters.) + // + // Fortunately, this is just for diagnostic information so we don't need to let perfect be the enemy of good. + // + private static String RemoveNamespaces(this String s) + { + int firstIndexOfColonColon = s.IndexOf("::"); + if (firstIndexOfColonColon == -1) + return s; + int lookBack = firstIndexOfColonColon - 1; + for (; ;) + { + if (lookBack < 0) + break; + if (!(Char.IsLetterOrDigit(s[lookBack]) || s[lookBack] == '_')) + break; + lookBack--; + } + s = s.Remove(lookBack + 1, firstIndexOfColonColon - lookBack + 1); + return s.RemoveNamespaces(); + } + + /// <summary> + /// Locate and lazily load debug info for the native app module overlapping given + /// virtual address. + /// </summary> + /// <param name="ip">Instruction pointer address (code address for the lookup)</param> + /// <param name="rva">Output VA relative to module base</param> + private static IDiaSession GetDiaSession(IntPtr ip, out int rva) + { + if (ip == IntPtr.Zero) + { + rva = -1; + return null; + } + + IntPtr moduleBase = RuntimeAugments.GetModuleFromPointer(ip); + if (moduleBase == IntPtr.Zero) + { + rva = -1; + return null; + } + + rva = (int)(ip.ToInt64() - moduleBase.ToInt64()); + + if (s_loadedModules == null) + { + // Lazily create the parallel arrays s_loadedModules and s_perModuleDebugInfo + int moduleCount = RuntimeAugments.GetLoadedModules(null); + + s_loadedModules = new IntPtr[moduleCount]; + s_perModuleDebugInfo = new IDiaSession[moduleCount]; + + // Actually read the module addresses into the array + RuntimeAugments.GetLoadedModules(s_loadedModules); + } + + // Locate module index based on base address + int moduleIndex = s_loadedModules.Length; + do + { + if (--moduleIndex < 0) + { + return null; + } + } + while(s_loadedModules[moduleIndex] != moduleBase); + + IDiaSession diaSession = s_perModuleDebugInfo[moduleIndex]; + if (diaSession != null) + { + return diaSession; + } + + string modulePath = RuntimeAugments.TryGetFullPathToApplicationModule(moduleBase); + if (modulePath == null) + { + return null; + } + + int indexOfLastDot = modulePath.LastIndexOf('.'); + if (indexOfLastDot == -1) + { + return null; + } + + IDiaDataSource diaDataSource = GetDiaDataSource(); + if (diaDataSource == null) + { + return null; + } + + // Look for .pdb next to .exe / dll - if it's not there, bail. + String pdbPath = modulePath.Substring(0, indexOfLastDot) + ".pdb"; + int hr = diaDataSource.LoadDataFromPdb(pdbPath); + if (hr != S_OK) + { + return null; + } + + hr = diaDataSource.OpenSession(out diaSession); + if (hr != S_OK) + { + return null; + } + + s_perModuleDebugInfo[moduleIndex] = diaSession; + return diaSession; + } + + // CoCreateInstance is not in WindowsApp_Downlevel.lib and ExactSpelling = true is required + // to force MCG to resolve it. + [DllImport("api-ms-win-core-com-l1-1-0.dll", ExactSpelling =true)] + private static extern unsafe int CoCreateInstance(byte* rclsid, IntPtr pUnkOuter, int dwClsContext, byte* riid, out IntPtr ppv); + + private const int S_OK = 0; + private const int CLSCTX_INPROC = 0x3; + + /// <summary> + /// Loaded binary module addresses. + /// </summary> + [ThreadStatic] + private static IntPtr[] s_loadedModules; + + /// <summary> + /// DIA session COM interfaces for the individual native application modules. + /// The array is constructed upon the first call to GetDiaSession but the + /// COM interface instances are created lazily on demand. + /// This array is parallel to s_loadedModules - it has the same number of elements + /// and the corresponding entries have the same indices. + /// </summary> + [ThreadStatic] + private static IDiaSession[] s_perModuleDebugInfo; + } +} + diff --git a/src/System.Private.StackTraceGenerator/src/System.Private.StackTraceGenerator.csproj b/src/System.Private.StackTraceGenerator/src/System.Private.StackTraceGenerator.csproj new file mode 100644 index 000000000..742117333 --- /dev/null +++ b/src/System.Private.StackTraceGenerator/src/System.Private.StackTraceGenerator.csproj @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build"> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" /> + <PropertyGroup> + <AssemblyName>System.Private.StackTraceGenerator</AssemblyName> + <OutputType>Library</OutputType> + <ProjectGuid>{35A616DA-EEC6-49C4-BD06-AD27938DC94B}</ProjectGuid> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> + </PropertyGroup> + <!-- Default configurations to help VS understand the options --> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' "> + <PlatformTarget>x86</PlatformTarget> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' "> + <PlatformTarget>x86</PlatformTarget> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|amd64' "> + <PlatformTarget>x64</PlatformTarget> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|amd64' "> + <PlatformTarget>x64</PlatformTarget> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|arm' "> + <PlatformTarget>arm</PlatformTarget> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|arm' "> + <PlatformTarget>arm</PlatformTarget> + </PropertyGroup> + + <ItemGroup> + <ProjectReference Include="$(MSBuildThisFileDirectory)..\..\System.Private.CoreLib\src\System.Private.CoreLib.csproj" /> + </ItemGroup> + + <ItemGroup> + <Compile Include="$(MSBuildThisFileDirectory)Internal\StackTraceGenerator\StackTraceGenerator.cs" /> + <Compile Include="$(MSBuildThisFileDirectory)Internal\StackTraceGenerator\Attributes.cs" /> + <Compile Include="$(MSBuildThisFileDirectory)Internal\LightweightInterop\ComInterface.cs" /> + <Compile Include="$(MSBuildThisFileDirectory)Internal\LightweightInterop\StdCall.cs" /> + <Compile Include="$(MSBuildThisFileDirectory)Internal\LightweightInterop\MarshalExtensions.cs" /> + <Compile Include="$(MSBuildThisFileDirectory)Internal\Dia\Guids.cs" /> + <Compile Include="$(MSBuildThisFileDirectory)Internal\Dia\StdCall.cs" /> + <Compile Include="$(MSBuildThisFileDirectory)Internal\Dia\DiaEnums.cs" /> + <Compile Include="$(MSBuildThisFileDirectory)Internal\Dia\DiaInterfaces.cs" /> + </ItemGroup> + <ItemGroup> + <Compile Include="$(MSBuildThisFileDirectory)..\..\Common\src\System\NotImplemented.cs" /> + </ItemGroup> + + <PropertyGroup> + <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <TargetPlatformIdentifier>Portable</TargetPlatformIdentifier> + <TargetFrameworkIdentifier>.NETPortable</TargetFrameworkIdentifier> + <TargetFrameworkProfile>Profile7</TargetFrameworkProfile> + <TargetFrameworkMonikerDisplayName>.NET Portable Subset</TargetFrameworkMonikerDisplayName> + <ImplicitlyExpandTargetFramework>false</ImplicitlyExpandTargetFramework> + </PropertyGroup> + + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" /> +</Project> |