diff --git a/src/coreclr/tools/aot/ILCompiler.Diagnostics/ILCompiler.Diagnostics.csproj b/src/coreclr/tools/aot/ILCompiler.Diagnostics/ILCompiler.Diagnostics.csproj index 97c97bb7de720d..0d1b23ad73b3c8 100644 --- a/src/coreclr/tools/aot/ILCompiler.Diagnostics/ILCompiler.Diagnostics.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Diagnostics/ILCompiler.Diagnostics.csproj @@ -15,6 +15,7 @@ binaries are up to date and which are stale. --> false Debug;Release;Checked + true diff --git a/src/coreclr/tools/aot/ILCompiler.Diagnostics/ILCompilerComWrappers.cs b/src/coreclr/tools/aot/ILCompiler.Diagnostics/ILCompilerComWrappers.cs new file mode 100644 index 00000000000000..d9972da41f76c6 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Diagnostics/ILCompilerComWrappers.cs @@ -0,0 +1,32 @@ + +using System; +using System.Collections; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Microsoft.DiaSymReader +{ + internal unsafe sealed class ILCompilerComWrappers : ComWrappers + { + protected override unsafe ComInterfaceEntry* ComputeVtables(object obj, CreateComInterfaceFlags flags, out int count) + { + // passing the managed object to COM is not currently supported + throw new NotImplementedException(); + } + + protected override object CreateObject(IntPtr externalComObject, CreateObjectFlags flags) + { + // Assert use of the UniqueInstance flag since the returned Native Object Wrapper always + // supports IDisposable and its Dispose will always release and suppress finalization. + // If the wrapper doesn't always support IDisposable the assert can be relaxed. + Debug.Assert(flags.HasFlag(CreateObjectFlags.UniqueInstance)); + + // Throw an exception if the type is not supported by the implementation. + // Null can be returned as well, but an ArgumentNullException will be thrown for + // the consumer of this ComWrappers instance. + return SymNgenWriterWrapper.CreateIfSupported(externalComObject) ?? throw new NotSupportedException(); + } + + protected override void ReleaseObjects(IEnumerable objects) => throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/coreclr/tools/aot/ILCompiler.Diagnostics/ISymNGenWriter.cs b/src/coreclr/tools/aot/ILCompiler.Diagnostics/ISymNGenWriter.cs index 4c4b6b97d60a91..40702fd187a75a 100644 --- a/src/coreclr/tools/aot/ILCompiler.Diagnostics/ISymNGenWriter.cs +++ b/src/coreclr/tools/aot/ILCompiler.Diagnostics/ISymNGenWriter.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#pragma warning disable 436 // SuppressUnmanagedCodeSecurityAttribute defined in source and mscorlib +#pragma warning disable 436 // SuppressUnmanagedCodeSecurityAttribute defined in source and mscorlib using System; using System.Collections.Generic; @@ -11,9 +11,39 @@ namespace Microsoft.DiaSymReader { - [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("D682FD12-43dE-411C-811B-BE8404CEA126"), SuppressUnmanagedCodeSecurity] + /// + /// IUnknown COM type for writing NGen PDBs + /// + /// + /// + /// [ + /// object, + /// uuid(d682fd12-43de-411c-811b-be8404cea126), + /// pointer_default(unique) + /// ] + /// interface ISymNGenWriter : IUnknown + /// { + /// /* + /// * Add a new public symbol to the NGEN PDB. + /// */ + /// HRESULT AddSymbol([in] BSTR pSymbol, + /// [in] USHORT iSection, + /// [in] ULONGLONG rva); + /// + /// /* + /// * Adds a new section to the NGEN PDB. + /// */ + /// HRESULT AddSection([in] USHORT iSection, + /// [in] USHORT flags, + /// [in] long offset, + /// [in] long cb); + /// }; + /// + /// internal interface ISymNGenWriter { + public static readonly Guid IID = new Guid("D682FD12-43dE-411C-811B-BE8404CEA126"); + // Add a new public symbol to the NGEN PDB. void AddSymbol([MarshalAs(UnmanagedType.BStr)] string pSymbol, ushort iSection, @@ -46,9 +76,46 @@ internal enum OMF : ushort } - [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("B029E51B-4C55-4fe2-B993-9F7BC1F10DB4"), SuppressUnmanagedCodeSecurity] + /// + /// IUnknown COM type for writing NGen PDBs + /// + /// + /// + /// [ + /// object, + /// local, + /// uuid(B029E51B-4C55-4fe2-B993-9F7BC1F10DB4), + /// pointer_default(unique) + /// ] + /// interface ISymNGenWriter2 : ISymNGenWriter + /// { + /// HRESULT OpenModW([in] const wchar_t* wszModule, + /// [in] const wchar_t* wszObjFile, + /// [out] BYTE** ppmod); + /// + /// HRESULT CloseMod([in] BYTE* pmod); + /// + /// HRESULT ModAddSymbols([in] BYTE* pmod, [in] BYTE* pbSym, [in] long cb); + /// + /// HRESULT ModAddSecContribEx( + /// [in] BYTE* pmod, + /// [in] USHORT isect, + /// [in] long off, + /// [in] long cb, + /// [in] ULONG dwCharacteristics, + /// [in] DWORD dwDataCrc, + /// [in] DWORD dwRelocCrc); + /// + /// HRESULT QueryPDBNameExW( + /// [out, size_is(cchMax)] wchar_t wszPDB[], + /// [in] SIZE_T cchMax); + /// }; + /// + /// internal interface ISymNGenWriter2 : ISymNGenWriter { + public readonly static new Guid IID = new Guid("B029E51B-4C55-4fe2-B993-9F7BC1F10DB4"); + // Add a new public symbol to the NGEN PDB. new void AddSymbol([MarshalAs(UnmanagedType.BStr)] string pSymbol, ushort iSection, @@ -78,7 +145,7 @@ void ModAddSecContribEx( uint dwRelocCrc); void QueryPDBNameExW( - [MarshalAs(UnmanagedType.LPWStr)] StringBuilder pdb, + [MarshalAs(UnmanagedType.LPWStr)] char[] pdb, IntPtr cchMax); } } diff --git a/src/coreclr/tools/aot/ILCompiler.Diagnostics/PdbWriter.cs b/src/coreclr/tools/aot/ILCompiler.Diagnostics/PdbWriter.cs index a16d5bf19a8e67..be0d8af909f727 100644 --- a/src/coreclr/tools/aot/ILCompiler.Diagnostics/PdbWriter.cs +++ b/src/coreclr/tools/aot/ILCompiler.Diagnostics/PdbWriter.cs @@ -89,7 +89,7 @@ public class PdbWriter Dictionary _documentToChecksumOffsetMapping; UIntPtr _pdbMod; - ISymNGenWriter2 _ngenWriter; + SymNgenWriterWrapper _ngenWriter; static PdbWriter() { @@ -116,7 +116,7 @@ private static IntPtr DllImportResolver(string libraryName, Assembly assembly, D private extern static void CreateNGenPdbWriter( [MarshalAs(UnmanagedType.LPWStr)] string ngenImagePath, [MarshalAs(UnmanagedType.LPWStr)] string pdbPath, - [MarshalAs(UnmanagedType.Interface)] out ISymNGenWriter2 ngenPdbWriter); + out IntPtr ngenPdbWriterPtr); public PdbWriter(string pdbPath, PDBExtraData pdbExtraData, TargetDetails target) { @@ -138,23 +138,14 @@ public void WritePDBData(string dllPath, IEnumerable methods) { try { - try - { - WritePDBDataHelper(dllPath, methods); - } - finally - { - if ((_ngenWriter != null) && (_pdbMod != UIntPtr.Zero)) - { - _ngenWriter.CloseMod(_pdbMod); - } - } + WritePDBDataHelper(dllPath, methods); } finally { - if (_ngenWriter != null) + if ((_ngenWriter != null) && (_pdbMod != UIntPtr.Zero)) { - Marshal.FinalReleaseComObject(_ngenWriter); + _ngenWriter.CloseMod(_pdbMod); + _ngenWriter?.Dispose(); } } @@ -217,14 +208,16 @@ private void WritePDBDataHelper(string dllPath, IEnumerable methods) // Delete any preexisting PDB file upfront, otherwise CreateNGenPdbWriter silently opens it File.Delete(_pdbFilePath); - CreateNGenPdbWriter(dllPath, _pdbFilePath, out _ngenWriter); + var comWrapper = new ILCompilerComWrappers(); + CreateNGenPdbWriter(dllPath, _pdbFilePath, out var pdbWriterInst); + _ngenWriter = (SymNgenWriterWrapper)comWrapper.GetOrCreateObjectForComInstance(pdbWriterInst, CreateObjectFlags.UniqueInstance); { // PDB file is now created. Get its path and update _pdbFilePath so the PDB file // can be deleted if we don't make it successfully to the end. - StringBuilder pdbFilePathBuilder = new StringBuilder(); - pdbFilePathBuilder.Capacity = 1024; - _ngenWriter.QueryPDBNameExW(pdbFilePathBuilder, new IntPtr(pdbFilePathBuilder.Capacity)); + const int capacity = 1024; + var pdbFilePathBuilder = new char[capacity]; + _ngenWriter.QueryPDBNameExW(pdbFilePathBuilder, new IntPtr(capacity - 1) /* remove 1 byte for null */); _pdbFilePath = pdbFilePathBuilder.ToString(); } @@ -428,9 +421,9 @@ private void WriteCompilerVersion() byte iLanguage = (byte)CV_CFL_LANG.CV_CFL_MSIL; writer.Write(iLanguage); // Write rest of flags - writer.Write((byte)0); - writer.Write((byte)0); - writer.Write((byte)0); + writer.Write((byte)0); + writer.Write((byte)0); + writer.Write((byte)0); switch (_target.Architecture) { diff --git a/src/coreclr/tools/aot/ILCompiler.Diagnostics/SymNgenWriterWrapper.cs b/src/coreclr/tools/aot/ILCompiler.Diagnostics/SymNgenWriterWrapper.cs new file mode 100644 index 00000000000000..8a1166f43a39bf --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Diagnostics/SymNgenWriterWrapper.cs @@ -0,0 +1,149 @@ + +using System; +using System.Runtime.InteropServices; +using System.Text; + +#nullable enable + +namespace Microsoft.DiaSymReader +{ + internal class SymNgenWriterWrapper : ISymNGenWriter2, IDisposable + { + private bool _isDisposed = false; + public IntPtr ISymNGenWriter2Inst { get; } + + private SymNgenWriterWrapper(IntPtr writer2Inst) + { + ISymNGenWriter2Inst = writer2Inst; + } + + public static SymNgenWriterWrapper? CreateIfSupported(IntPtr ptr) + { + var iid = ISymNGenWriter2.IID; + int hr = Marshal.QueryInterface(ptr, ref iid, out IntPtr ngenWriterInst); + if (hr != 0) + { + return null; + } + + return new SymNgenWriterWrapper(ngenWriterInst); + } + + ~SymNgenWriterWrapper() + { + DisposeInternal(); + } + + public void Dispose() + { + DisposeInternal(); + GC.SuppressFinalize(this); + } + + private void DisposeInternal() + { + if (_isDisposed) + { + return; + } + Marshal.Release(ISymNGenWriter2Inst); + _isDisposed = true; + } + + public unsafe void AddSymbol(string pSymbol, ushort iSection, ulong rva) + { + IntPtr strLocal = Marshal.StringToBSTR(pSymbol); + var inst = ISymNGenWriter2Inst; + var func = (delegate* unmanaged)(*(*(void***)inst + 3 /* ISymNGenWriter2.AddSymbol slot */)); + int hr = func(inst, strLocal, iSection, rva); + Marshal.FreeBSTR(strLocal); + if (hr != 0) + { + Marshal.ThrowExceptionForHR(hr); + } + } + + public unsafe void AddSection(ushort iSection, OMF flags, int offset, int cb) + { + var inst = ISymNGenWriter2Inst; + var func = (delegate* unmanaged)(*(*(void***)inst + 4)); + int hr = func(inst, iSection, flags, offset, cb); + if (hr != 0) + { + Marshal.ThrowExceptionForHR(hr); + } + } + + public unsafe void OpenModW(string wszModule, string wszObjFile, out UIntPtr ppmod) + { + var inst = ISymNGenWriter2Inst; + fixed (char* wszModulePtr = wszModule) + fixed (char* wszObjFilePtr = wszObjFile) + { + UIntPtr ppmodPtr; + var func = (delegate* unmanaged)(*(*(void***)inst + 5)); + int hr = func(inst, wszModulePtr, wszObjFilePtr, &ppmodPtr); + ppmod = ppmodPtr; + if (hr != 0) + { + Marshal.ThrowExceptionForHR(hr); + } + } + } + + public unsafe void CloseMod(UIntPtr pmod) + { + var inst = ISymNGenWriter2Inst; + var func = (delegate* unmanaged)(*(*(void***)inst + 6)); + int hr = func(inst, pmod); + if (hr != 0) + { + Marshal.ThrowExceptionForHR(hr); + } + } + + public unsafe void ModAddSymbols(UIntPtr pmod, byte[] pbSym, int cb) + { + fixed (byte* pbSymPtr = pbSym) + { + var pbSymLocal = (IntPtr)pbSymPtr; + var inst = ISymNGenWriter2Inst; + var func = (delegate* unmanaged)(*(*(void***)inst + 7)); + int hr = func(inst, pmod, pbSymLocal, cb); + if (hr != 0) + { + Marshal.ThrowExceptionForHR(hr); + } + } + } + + public unsafe void ModAddSecContribEx(UIntPtr pmod, ushort isect, int off, int cb, uint dwCharacteristics, uint dwDataCrc, uint dwRelocCrc) + { + var inst = ISymNGenWriter2Inst; + var func = (delegate* unmanaged)(*(*(void***)inst + 8)); + int hr = func(inst, pmod, isect, off, cb, dwCharacteristics, dwDataCrc, dwRelocCrc); + if (hr != 0) + { + Marshal.ThrowExceptionForHR(hr); + } + } + + public unsafe void QueryPDBNameExW(char[] pdb, IntPtr cchMax) + { + fixed (char* pdbPtr = pdb) + { + var pdbLocal = (IntPtr)pdbPtr; + var inst = ISymNGenWriter2Inst; + var func = (delegate* unmanaged)(*(*(void***)inst + 9)); + int hr = func(inst, pdbPtr, cchMax); + if (hr != 0) + { + Marshal.ThrowExceptionForHR(hr); + } + } + } + + void ISymNGenWriter.AddSymbol(string pSymbol, ushort iSection, ulong rva) => AddSymbol(pSymbol, iSection, rva); + void ISymNGenWriter.AddSection(ushort iSection, OMF flags, int offset, int cb) => AddSection(iSection, flags, offset, cb); + } +}