1
0
Fork 0
mirror of https://github.com/VSadov/Satori.git synced 2025-06-10 18:11:04 +09:00

Enable basic generation of ngen pdb from composite image (#36311)

* Enable basic generation of ngen pdb from composite image
- Port non-line number handling portion of ngen pdb from crossgen to r2rdump
  - This pdb generation logic may be used for both composite and non-composite R2R images
  - Only pdb generation is supported. Perfmap generation for unix is not supported
  - pdb generation is only supported on Windows x86 and amd64 platforms. Diasymreader is not supported on other systems
  - Pdb generation does not generation symbols with the same names as crossgen. Instead it uses the name generator from r2rdump. For current needs this should be sufficient
- Update composite file format so that pdb generation process will work. Major difference is that a CorHeader is always produced for composite images now. This CorHeader is not used by the runtime, but is required for DiaSymReader to generate an ngen pdb.
This commit is contained in:
David Wrighton 2020-05-15 12:31:27 -07:00 committed by GitHub
parent 363ac3692c
commit b764cae5f8
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 751 additions and 100 deletions

View file

@ -28,8 +28,9 @@ in the COFF header represent a full copy of the input IL and MSIL metadata it wa
**Composite R2R files** currently conform to Windows PE executable file format as the
native envelope. Moving forward we plan to gradually add support for platform-native
executable formats (ELF on Linux, MachO on OSX) as the native envelopes. As a natural corollary
there is no global CLI / COR header in the file. The ReadyToRun header structure is pointed to
executable formats (ELF on Linux, MachO on OSX) as the native envelopes. There is a
global CLI / COR header in the file, but it only exists to facilitate pdb generation, and does
not participate in any usages by the CoreCLR runtime. The ReadyToRun header structure is pointed to
by the well-known export symbol `RTR_HEADER` and has the `READYTORUN_FLAG_COMPOSITE` flag set.
Input MSIL metadata and IL streams can be either embedded in the composite R2R file or left

View file

@ -0,0 +1,35 @@
<!-- Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE file in the project root for more information. -->
<Project>
<PropertyGroup>
<!--
Ensure the binaries using DiaSymReader.Native are marked as AnyCPU unless they specify
a different target. This should be the default behavior but recent SDK changes to more
correctly consider RIDs caused our behavior to change here. Once the SDK logic is settled
here we can remove this.
https://github.com/dotnet/sdk/issues/3495
-->
<PlatformTarget Condition="'$(PlatformTarget)' == ''">AnyCPU</PlatformTarget>
</PropertyGroup>
<!--
This is adding the diasymreader native assets to the output directory of our binaries. The
package can't be referenced directly but rather has to have it's assets manually copied
out. This logic is responsible for doing that.
-->
<ItemGroup Condition="'$(DotNetBuildFromSource)' != 'true'">
<Content Include="$(NuGetPackageRoot)\microsoft.diasymreader.native\$(MicrosoftDiaSymReaderNativeVersion)\runtimes\win\native\Microsoft.DiaSymReader.Native.x86.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Visible>false</Visible>
<Pack>false</Pack>
</Content>
<Content Include="$(NuGetPackageRoot)\microsoft.diasymreader.native\$(MicrosoftDiaSymReaderNativeVersion)\runtimes\win\native\Microsoft.DiaSymReader.Native.amd64.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Visible>false</Visible>
<Pack>false</Pack>
</Content>
<PackageReference Include="Microsoft.DiaSymReader.Native" Version="$(MicrosoftDiaSymReaderNativeVersion)" ExcludeAssets="all"/>
</ItemGroup>
</Project>

View file

@ -150,8 +150,6 @@ namespace ILCompiler.DependencyAnalysis
if (node is NativeDebugDirectoryEntryNode nddeNode)
{
// There should be only one NativeDebugDirectoryEntry.
// This assert will need to be revisited when we implement the composite R2R format, where we'll need to figure
// out how native symbols will be emitted, and verify that the DiaSymReader library is able to consume them.
Debug.Assert(nativeDebugDirectoryEntryNode == null);
nativeDebugDirectoryEntryNode = nddeNode;
}
@ -176,11 +174,8 @@ namespace ILCompiler.DependencyAnalysis
EmitObjectData(r2rPeBuilder, nodeContents, nodeIndex, name, node.Section, _mapFileBuilder);
}
if (!_nodeFactory.CompilationModuleGroup.IsCompositeBuildMode || _componentModule != null)
{
r2rPeBuilder.SetCorHeader(_nodeFactory.CopiedCorHeaderNode, _nodeFactory.CopiedCorHeaderNode.Size);
r2rPeBuilder.SetDebugDirectory(_nodeFactory.DebugDirectoryNode, _nodeFactory.DebugDirectoryNode.Size);
}
r2rPeBuilder.SetCorHeader(_nodeFactory.CopiedCorHeaderNode, _nodeFactory.CopiedCorHeaderNode.Size);
r2rPeBuilder.SetDebugDirectory(_nodeFactory.DebugDirectoryNode, _nodeFactory.DebugDirectoryNode.Size);
if (_nodeFactory.Win32ResourcesNode != null)
{

View file

@ -51,7 +51,10 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun
public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
{
sb.Append(nameMangler.CompilationUnitPrefix);
sb.Append($"__CorHeader_{_module.Assembly.GetName().Name}");
if (_module != null)
sb.Append($"__CorHeader_{_module.Assembly.GetName().Name}");
else
sb.Append("__CompositeCorHeader_");
}
protected override string GetName(NodeFactory factory) => this.GetMangledName(factory.NameMangler);
@ -62,75 +65,116 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun
builder.RequireInitialPointerAlignment();
builder.AddSymbol(this);
BlobReader reader = _module.PEReader.GetEntireImage().GetReader();
reader.Offset = _module.PEReader.PEHeaders.CorHeaderStartOffset;
// Header Size
int headerSize = reader.ReadInt32();
builder.EmitInt(headerSize);
// Runtime major, minor version
builder.EmitUShort(reader.ReadUInt16());
builder.EmitUShort(reader.ReadUInt16());
// Metadata Directory
ReadDirectoryEntry(ref reader);
var metadataBlob = factory.CopiedMetadataBlob(_module);
builder.EmitReloc(metadataBlob, RelocType.IMAGE_REL_BASED_ADDR32NB);
builder.EmitInt(metadataBlob.Size);
// Flags
builder.EmitUInt((uint)(((CorFlags)reader.ReadUInt32() & ~CorFlags.ILOnly) | CorFlags.ILLibrary));
// Entrypoint
builder.EmitInt(reader.ReadInt32());
// Resources Directory
if (ReadDirectoryEntry(ref reader).Size > 0)
if (_module != null)
{
var managedResources = factory.CopiedManagedResources(_module);
builder.EmitReloc(managedResources, RelocType.IMAGE_REL_BASED_ADDR32NB);
builder.EmitInt(managedResources.Size);
BlobReader reader = _module.PEReader.GetEntireImage().GetReader();
reader.Offset = _module.PEReader.PEHeaders.CorHeaderStartOffset;
// Header Size
int headerSize = reader.ReadInt32();
builder.EmitInt(headerSize);
// Runtime major, minor version
builder.EmitUShort(reader.ReadUInt16());
builder.EmitUShort(reader.ReadUInt16());
// Metadata Directory
ReadDirectoryEntry(ref reader);
var metadataBlob = factory.CopiedMetadataBlob(_module);
builder.EmitReloc(metadataBlob, RelocType.IMAGE_REL_BASED_ADDR32NB);
builder.EmitInt(metadataBlob.Size);
// Flags
builder.EmitUInt((uint)(((CorFlags)reader.ReadUInt32() & ~CorFlags.ILOnly) | CorFlags.ILLibrary));
// Entrypoint
builder.EmitInt(reader.ReadInt32());
// Resources Directory
if (ReadDirectoryEntry(ref reader).Size > 0)
{
var managedResources = factory.CopiedManagedResources(_module);
builder.EmitReloc(managedResources, RelocType.IMAGE_REL_BASED_ADDR32NB);
builder.EmitInt(managedResources.Size);
}
else
{
WriteEmptyDirectoryEntry(ref builder);
}
// Strong Name Signature Directory
if (ReadDirectoryEntry(ref reader).Size > 0)
{
var strongNameSignature = factory.CopiedStrongNameSignature(_module);
builder.EmitReloc(strongNameSignature, RelocType.IMAGE_REL_BASED_ADDR32NB);
builder.EmitInt(strongNameSignature.Size);
}
else
{
WriteEmptyDirectoryEntry(ref builder);
}
// Code Manager Table Directory
ReadDirectoryEntry(ref reader);
WriteEmptyDirectoryEntry(ref builder);
// VTable Fixups Directory
ReadDirectoryEntry(ref reader);
WriteEmptyDirectoryEntry(ref builder);
// Export Address Table Jumps Directory
ReadDirectoryEntry(ref reader);
WriteEmptyDirectoryEntry(ref builder);
// Managed Native (ReadyToRun) Header Directory
ReadDirectoryEntry(ref reader);
builder.EmitReloc(factory.Header, RelocType.IMAGE_REL_BASED_ADDR32NB);
builder.EmitReloc(factory.Header, RelocType.IMAGE_REL_SYMBOL_SIZE);
// Did we fully read the header?
Debug.Assert(reader.Offset - headerSize == _module.PEReader.PEHeaders.CorHeaderStartOffset);
Debug.Assert(builder.CountBytes == headerSize);
Debug.Assert(headerSize == Size);
}
else
{
// Generating CORHeader for composite image
// Header Size
builder.EmitInt(Size);
// Runtime major, minor version
builder.EmitUShort(0);
builder.EmitUShort(0);
// Metadata Directory
builder.EmitReloc(factory.ManifestMetadataTable, RelocType.IMAGE_REL_BASED_ADDR32NB);
builder.EmitReloc(factory.ManifestMetadataTable, RelocType.IMAGE_REL_SYMBOL_SIZE);
// Flags
builder.EmitUInt(0);
// Entrypoint
builder.EmitInt(0);
// Resources Directory
WriteEmptyDirectoryEntry(ref builder);
}
// Strong Name Signature Directory
if (ReadDirectoryEntry(ref reader).Size > 0)
{
var strongNameSignature = factory.CopiedStrongNameSignature(_module);
builder.EmitReloc(strongNameSignature, RelocType.IMAGE_REL_BASED_ADDR32NB);
builder.EmitInt(strongNameSignature.Size);
}
else
{
// Strong Name Signature Directory
WriteEmptyDirectoryEntry(ref builder);
// Code Manager Table Directory
WriteEmptyDirectoryEntry(ref builder);
// VTable Fixups Directory
WriteEmptyDirectoryEntry(ref builder);
// Export Address Table Jumps Directory
WriteEmptyDirectoryEntry(ref builder);
// Managed Native (ReadyToRun) Header Directory
builder.EmitReloc(factory.Header, RelocType.IMAGE_REL_BASED_ADDR32NB);
builder.EmitReloc(factory.Header, RelocType.IMAGE_REL_SYMBOL_SIZE);
}
// Code Manager Table Directory
ReadDirectoryEntry(ref reader);
WriteEmptyDirectoryEntry(ref builder);
// VTable Fixups Directory
ReadDirectoryEntry(ref reader);
WriteEmptyDirectoryEntry(ref builder);
// Export Address Table Jumps Directory
ReadDirectoryEntry(ref reader);
WriteEmptyDirectoryEntry(ref builder);
// Managed Native (ReadyToRun) Header Directory
ReadDirectoryEntry(ref reader);
builder.EmitReloc(factory.Header, RelocType.IMAGE_REL_BASED_ADDR32NB);
builder.EmitReloc(factory.Header, RelocType.IMAGE_REL_SYMBOL_SIZE);
// Did we fully read the header?
Debug.Assert(reader.Offset - headerSize == _module.PEReader.PEHeaders.CorHeaderStartOffset);
Debug.Assert(builder.CountBytes == headerSize);
Debug.Assert(headerSize == Size);
return builder.ToObjectData();
}
@ -143,6 +187,17 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun
public override int CompareToImpl(ISortableNode other, CompilerComparer comparer)
{
if (_module == null)
{
if (((CopiedCorHeaderNode)other)._module == null)
return 0;
return -1;
}
else if (((CopiedCorHeaderNode)other)._module == null)
{
return 1;
}
return _module.CompareTo(((CopiedCorHeaderNode)other)._module);
}
}

View file

@ -56,14 +56,18 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun
public unsafe int Size => RSDSSize;
public NativeDebugDirectoryEntryNode(EcmaModule sourceModule)
: base(sourceModule)
{ }
public NativeDebugDirectoryEntryNode(string pdbName)
: base(null)
{
_pdbName = pdbName;
}
private string _pdbName;
public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
{
sb.Append(nameMangler.CompilationUnitPrefix);
sb.Append($"__NativeRvaBlob_{_module.Assembly.GetName().Name}");
sb.Append($"__NativeDebugDirectory_{_pdbName.Replace('.','_')}");
}
public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
@ -99,7 +103,7 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun
// Age
writer.Write(1);
string pdbFileName = _module.Assembly.GetName().Name + ".ni.pdb";
string pdbFileName = _pdbName;
byte[] pdbFileNameBytes = Encoding.UTF8.GetBytes(pdbFileName);
writer.Write(pdbFileNameBytes);
@ -107,6 +111,11 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun
return rsdsEntry.ToArray();
}
}
public override int CompareToImpl(ISortableNode other, CompilerComparer comparer)
{
return _pdbName.CompareTo(((NativeDebugDirectoryEntryNode)other)._pdbName);
}
}
public class CopiedDebugDirectoryEntryNode : DebugDirectoryEntryNode

View file

@ -6,6 +6,7 @@
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Reflection.PortableExecutable;
using Internal.Text;
using Internal.TypeSystem.Ecma;
@ -25,10 +26,17 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun
sizeof(int); // PointerToRawData
private EcmaModule _module;
private NativeDebugDirectoryEntryNode _nativeEntry;
public DebugDirectoryNode(EcmaModule sourceModule)
public DebugDirectoryNode(EcmaModule sourceModule, string outputFileName)
{
_module = sourceModule;
string pdbNameRoot = Path.GetFileNameWithoutExtension(outputFileName);
if (sourceModule != null)
{
pdbNameRoot = sourceModule.Assembly.GetName().Name;
}
_nativeEntry = new NativeDebugDirectoryEntryNode(pdbNameRoot + ".ni.pdb");
}
public override ObjectNodeSection Section => ObjectNodeSection.TextSection;
@ -48,13 +56,22 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun
public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
{
sb.Append(nameMangler.CompilationUnitPrefix);
sb.Append($"__DebugDirectory_{_module.Assembly.GetName().Name}");
string directoryName;
if (_module != null)
directoryName = _module.Assembly.GetName().Name;
else
directoryName = "Composite";
sb.Append($"__DebugDirectory_{directoryName}");
}
protected override string GetName(NodeFactory factory) => this.GetMangledName(factory.NameMangler);
int GetNumDebugDirectoryEntriesInModule()
{
if (_module == null)
return 0;
ImmutableArray<DebugDirectoryEntry> entries = _module.PEReader.ReadDebugDirectory();
return entries == null ? 0 : entries.Length;
}
@ -65,12 +82,15 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun
builder.RequireInitialPointerAlignment();
builder.AddSymbol(this);
ImmutableArray<DebugDirectoryEntry> entries = _module.PEReader.ReadDebugDirectory();
ImmutableArray<DebugDirectoryEntry> entries = default(ImmutableArray<DebugDirectoryEntry>);
if (_module != null)
entries = _module.PEReader.ReadDebugDirectory();
int numEntries = GetNumDebugDirectoryEntriesInModule();
// First, write the native debug directory entry
{
var entry = (NativeDebugDirectoryEntryNode)factory.DebugDirectoryEntry(_module, -1);
var entry = _nativeEntry;
builder.EmitUInt(0 /* Characteristics */);
if (numEntries > 0)
@ -121,6 +141,17 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun
public override int CompareToImpl(ISortableNode other, CompilerComparer comparer)
{
if (_module == null)
{
if (((DebugDirectoryNode)other)._module == null)
return 0;
return -1;
}
else if (((DebugDirectoryNode)other)._module == null)
{
return 1;
}
return _module.CompareTo(((DebugDirectoryNode)other)._module);
}
}

View file

@ -133,11 +133,13 @@ namespace ILCompiler.DependencyAnalysis
Module = module;
}
public bool Equals(ModuleAndIntValueKey other) => IntValue == other.IntValue && Module.Equals(other.Module);
public bool Equals(ModuleAndIntValueKey other) => IntValue == other.IntValue && ((Module == null && other.Module == null) || Module.Equals(other.Module));
public override bool Equals(object obj) => obj is ModuleAndIntValueKey && Equals((ModuleAndIntValueKey)obj);
public override int GetHashCode()
{
int hashCode = IntValue * 0x5498341 + 0x832424;
if (Module == null)
return hashCode;
return hashCode * 23 + Module.GetHashCode();
}
}
@ -255,9 +257,6 @@ namespace ILCompiler.DependencyAnalysis
_debugDirectoryEntries = new NodeCache<ModuleAndIntValueKey, DebugDirectoryEntryNode>(key =>
{
if (key.IntValue < 0)
return new NativeDebugDirectoryEntryNode(key.Module);
else
return new CopiedDebugDirectoryEntryNode(key.Module, key.IntValue);
});
@ -657,10 +656,7 @@ namespace ILCompiler.DependencyAnalysis
graph.AddRoot(PrecodeImports, "Precode helper imports are always generated");
graph.AddRoot(StringImports, "String imports are always generated");
graph.AddRoot(Header, "ReadyToRunHeader is always generated");
if (!CompilationModuleGroup.IsCompositeBuildMode)
{
graph.AddRoot(CopiedCorHeaderNode, "MSIL COR header is always generated for single-file R2R files");
}
graph.AddRoot(CopiedCorHeaderNode, "MSIL COR header is always generated for R2R files");
graph.AddRoot(DebugDirectoryNode, "Debug Directory will always contain at least one entry");
if (Win32ResourcesNode != null)

View file

@ -302,7 +302,7 @@ namespace ILCompiler
EcmaModule inputModule = NodeFactory.TypeSystemContext.GetModuleFromPath(inputFile);
CopiedCorHeaderNode copiedCorHeader = new CopiedCorHeaderNode(inputModule);
DebugDirectoryNode debugDirectory = new DebugDirectoryNode(inputModule);
DebugDirectoryNode debugDirectory = new DebugDirectoryNode(inputModule, outputFile);
NodeFactory componentFactory = new NodeFactory(
_nodeFactory.TypeSystemContext,
_nodeFactory.CompilationModuleGroup,

View file

@ -28,6 +28,7 @@ namespace ILCompiler
private InstructionSetSupport _instructionSetSupport;
private string _jitPath;
private string _outputFile;
// These need to provide reasonable defaults so that the user can optionally skip
// calling the Use/Configure methods and still get something reasonable back.
@ -115,13 +116,20 @@ namespace ILCompiler
return this;
}
public ReadyToRunCodegenCompilationBuilder GenerateOutputFile(string outputFile)
{
_outputFile = outputFile;
return this;
}
public override ICompilation ToCompilation()
{
// TODO: only copy COR headers for single-assembly build and for composite build with embedded MSIL
IEnumerable<EcmaModule> inputModules = _compilationGroup.CompilationModuleSet;
CopiedCorHeaderNode corHeaderNode = (_compilationGroup.IsCompositeBuildMode ? null : new CopiedCorHeaderNode(inputModules.First()));
EcmaModule singleModule = _compilationGroup.IsCompositeBuildMode ? null : inputModules.First();
CopiedCorHeaderNode corHeaderNode = new CopiedCorHeaderNode(singleModule);
// TODO: proper support for multiple input files
DebugDirectoryNode debugDirectoryNode = new DebugDirectoryNode(inputModules.First());
DebugDirectoryNode debugDirectoryNode = new DebugDirectoryNode(singleModule, _outputFile);
// Produce a ResourceData where the IBC PROFILE_DATA entry has been filtered out
// TODO: proper support for multiple input files

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<AssemblyName>ILCompiler.ReadyToRun</AssemblyName>

View file

@ -411,14 +411,20 @@ namespace ILCompiler.Reflection.ReadyToRun
{
if ((PEReader.PEHeaders.CorHeader.Flags & CorFlags.ILLibrary) == 0)
{
throw new BadImageFormatException("The file is not a ReadyToRun image");
if (!TryLocateNativeReadyToRunHeader())
throw new BadImageFormatException("The file is not a ReadyToRun image");
Debug.Assert(Composite);
}
else
{
_assemblyCache.Add(metadata);
DirectoryEntry r2rHeaderDirectory = PEReader.PEHeaders.CorHeader.ManagedNativeHeaderDirectory;
_readyToRunHeaderRVA = r2rHeaderDirectory.RelativeVirtualAddress;
Debug.Assert(!Composite);
}
_assemblyCache.Add(metadata);
DirectoryEntry r2rHeaderDirectory = PEReader.PEHeaders.CorHeader.ManagedNativeHeaderDirectory;
_readyToRunHeaderRVA = r2rHeaderDirectory.RelativeVirtualAddress;
Debug.Assert(!Composite);
}
else if (!TryLocateNativeReadyToRunHeader())
{

View file

@ -476,6 +476,7 @@ namespace ILCompiler
.UseParallelism(_commandLineOptions.Parallelism)
.UseJitPath(_commandLineOptions.JitPath)
.UseInstructionSetSupport(instructionSetSupport)
.GenerateOutputFile(_commandLineOptions.OutputFilePath.FullName)
.UseILProvider(ilProvider)
.UseBackendOptions(_commandLineOptions.CodegenOptions)
.UseLogger(logger)

View file

@ -36,6 +36,8 @@ namespace R2RDump
command.AddOption(new Option(new[] { "--referencePath", "--rp" }, "Search paths for reference assemblies", new Argument<DirectoryInfo[]>()));
command.AddOption(new Option(new[] { "--inlineSignatureBinary", "--isb" }, "Embed binary signature into its textual representation", new Argument<bool>()));
command.AddOption(new Option(new[] { "--signatureBinary", "--sb" }, "Append signature binary to its textual representation", new Argument<bool>()));
command.AddOption(new Option(new[] { "--create-pdb" }, "Create PDB", new Argument<bool>()));
command.AddOption(new Option(new[] { "--pdb-path" }, "PDB output path for --createpdb", new Argument<string>()));
return command;
}
}

View file

@ -0,0 +1,85 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#pragma warning disable 436 // SuppressUnmanagedCodeSecurityAttribute defined in source and mscorlib
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
namespace Microsoft.DiaSymReader
{
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("D682FD12-43dE-411C-811B-BE8404CEA126"), SuppressUnmanagedCodeSecurity]
internal interface ISymNGenWriter
{
// Add a new public symbol to the NGEN PDB.
void AddSymbol([MarshalAs(UnmanagedType.BStr)] string pSymbol,
ushort iSection,
ulong rva);
// Adds a new section to the NGEN PDB.
void AddSection(ushort iSection,
OMF flags,
int offset,
int cb);
}
[Flags]
internal enum OMF : ushort
{
Const_Read = 0x0001,
Const_Write = 0x0002,
Const_Exec = 0x0004,
Const_F32Bit = 0x0008,
Const_ReservedBits1 = 0x00f0,
Const_FSel = 0x0100,
Const_FAbs = 0x0200,
Const_ReservedBits2 = 0x0C00,
Const_FGroup = 0x1000,
Const_ReservedBits3 = 0xE000,
StandardText = (Const_FSel|Const_F32Bit|Const_Exec|Const_Read), // 0x10D
SentinelType = (Const_FAbs|Const_F32Bit) // 0x208
}
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("B029E51B-4C55-4fe2-B993-9F7BC1F10DB4"), SuppressUnmanagedCodeSecurity]
internal interface ISymNGenWriter2 : ISymNGenWriter
{
// Add a new public symbol to the NGEN PDB.
new void AddSymbol([MarshalAs(UnmanagedType.BStr)] string pSymbol,
ushort iSection,
ulong rva);
// Adds a new section to the NGEN PDB.
new void AddSection(ushort iSection,
OMF flags,
int offset,
int cb);
void OpenModW([MarshalAs(UnmanagedType.LPWStr)] string wszModule,
[MarshalAs(UnmanagedType.LPWStr)] string wszObjFile,
out UIntPtr ppmod);
void CloseMod(UIntPtr pmod);
void ModAddSymbols(UIntPtr pmod, [MarshalAs(UnmanagedType.LPArray)] byte[] pbSym, int cb);
void ModAddSecContribEx(
UIntPtr pmod,
ushort isect,
int off,
int cb,
uint dwCharacteristics,
uint dwDataCrc,
uint dwRelocCrc);
void QueryPDBNameExW(
[MarshalAs(UnmanagedType.LPWStr)] StringBuilder pdb,
IntPtr cchMax);
}
}

View file

@ -0,0 +1,394 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection.PortableExecutable;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.DiaSymReader;
namespace ILCompiler.PdbWriter
{
// NGEN always generates PDBs with public symbols lists (so tools can map IP ranges to
// methods). This bitmask indicates what extra info should be added to the PDB
[Flags]
public enum PDBExtraData
{
None = 0,
// Add string table subsection, files checksum subsection, and lines subsection to
// allow tools to map IP ranges to source lines.
kPDBLines = 0x00000001,
};
struct MethodInfo
{
public string AssemblyName;
public uint MethodToken;
public uint HotRVA;
public string Name;
public uint ColdRVA;
}
interface IModuleData
{
IEnumerable<MethodInfo> Methods { get; }
}
public enum SymChecksumType : byte
{
None = 0, // indicates no checksum is available
MD5,
SHA1,
SHA_256,
};
class SymDocument : IEquatable<SymDocument>
{
public string Name;
public SymChecksumType ChecksumType;
public byte[] Checksum;
public override int GetHashCode()
{
return Name.GetHashCode();
}
public override bool Equals(object other)
{
if (other is SymDocument documentOther)
{
return Equals(documentOther);
}
return false;
}
public bool Equals(SymDocument other)
{
if (Name != other.Name)
return false;
if (ChecksumType != other.ChecksumType)
return false;
if (Checksum.Length != other.Checksum.Length)
return false;
for (int i = 0; i < Checksum.Length; i++)
{
if (Checksum[i] != other.Checksum[i])
return false;
}
return true;
}
}
class PdbWriter
{
string _pdbPath;
PDBExtraData _pdbExtraData;
string _pdbFilePath;
string _tempSourceDllName;
List<SymDocument> _symDocuments = new List<SymDocument>();
Dictionary<string,int> _stringTableToOffsetMapping;
Dictionary<SymDocument,int> _documentToChecksumOffsetMapping;
UIntPtr _pdbMod;
ISymNGenWriter2 _ngenWriter;
private const string DiaSymReaderModuleName32 = "Microsoft.DiaSymReader.Native.x86.dll";
private const string DiaSymReaderModuleName64 = "Microsoft.DiaSymReader.Native.amd64.dll";
private const string CreateNGenPdbWriterFactoryName = "CreateNGenPdbWriter";
[DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.SafeDirectories)]
[DllImport(DiaSymReaderModuleName32, EntryPoint = CreateNGenPdbWriterFactoryName, PreserveSig = false)]
private extern static void CreateNGenPdbWriter32([MarshalAs(UnmanagedType.LPWStr)] string ngenImagePath, [MarshalAs(UnmanagedType.LPWStr)] string pdbPath, [MarshalAs(UnmanagedType.IUnknown)] out object ngenPdbWriter);
[DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.SafeDirectories)]
[DllImport(DiaSymReaderModuleName64, EntryPoint = CreateNGenPdbWriterFactoryName, PreserveSig = false)]
private extern static void CreateNGenPdbWriter64([MarshalAs(UnmanagedType.LPWStr)] string ngenImagePath, [MarshalAs(UnmanagedType.LPWStr)] string pdbPath, [MarshalAs(UnmanagedType.IUnknown)] out object ngenPdbWriter);
private static ISymNGenWriter2 CreateNGenWriter(string ngenImagePath, string pdbPath)
{
object instance;
if (IntPtr.Size == 4)
{
CreateNGenPdbWriter32(ngenImagePath, pdbPath, out instance);
}
else
{
CreateNGenPdbWriter64(ngenImagePath, pdbPath, out instance);
}
return (ISymNGenWriter2)instance;
}
public PdbWriter(string pdbPath, PDBExtraData pdbExtraData)
{
SymDocument unknownDocument = new SymDocument();
unknownDocument.Name = "unknown";
unknownDocument.ChecksumType = SymChecksumType.None;
unknownDocument.Checksum = Array.Empty<byte>();
_symDocuments.Add(unknownDocument);
_pdbPath = pdbPath;
_pdbExtraData = pdbExtraData;
}
public void WritePDBData(string dllPath, IEnumerable<MethodInfo> methods)
{
bool failed = true;
try
{
try
{
try
{
WritePDBDataHelper(dllPath, methods);
}
finally
{
if ((_ngenWriter != null) && (_pdbMod != UIntPtr.Zero))
{
_ngenWriter.CloseMod(_pdbMod);
}
}
}
finally
{
if (_ngenWriter != null)
{
Marshal.FinalReleaseComObject(_ngenWriter);
}
}
failed = false;
}
finally
{
if (_tempSourceDllName != null)
{
try
{
File.Delete(_tempSourceDllName);
}
catch {}
}
if (failed && (_pdbFilePath != null))
{
try
{
// If anything fails, do not create a partial pdb file
File.Delete(_pdbFilePath);
}
catch {}
}
}
}
private void WritePDBDataHelper(string dllPath, IEnumerable<MethodInfo> methods)
{
// This will try to open the managed PDB if lines info was requested. This is a
// likely failure point, so intentionally do this before creating the NGEN PDB file
// on disk.
bool isILPDBProvided = false;
if (_pdbExtraData.HasFlag(PDBExtraData.kPDBLines))
{
// line mapping not ported from crossgen yet.
throw new NotImplementedException();
}
string originalDllPath = dllPath;
// Currently DiaSymReader does not work properly generating NGEN PDBS unless
// the DLL whose PDB is being generated ends in .ni.*. Unfortunately, readyToRun
// images do not follow this convention and end up producing bad PDBS. To fix
// this (without changing diasymreader.dll which ships indepdendently of .NET Core)
// we copy the file to somethign with this convention before generating the PDB
// and delete it when we are done.
if (!dllPath.EndsWith(".ni.dll", StringComparison.OrdinalIgnoreCase) && !dllPath.EndsWith(".ni.exe", StringComparison.OrdinalIgnoreCase))
{
_tempSourceDllName = Path.Combine(Path.GetDirectoryName(dllPath), Path.GetFileNameWithoutExtension(dllPath) + ".ni" + Path.GetExtension(dllPath));
File.Copy(dllPath, _tempSourceDllName);
dllPath = _tempSourceDllName;
}
_ngenWriter = CreateNGenWriter(dllPath, _pdbPath + "\\");
{
// PDB file is now created. Get its path and initialize _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));
_pdbFilePath = pdbFilePathBuilder.ToString();
}
_ngenWriter.OpenModW(originalDllPath, Path.GetFileName(originalDllPath), out _pdbMod);
WriteStringTable();
WriteFileChecksums();
ushort? iCodeSection = null;
uint rvaOfTextSection = 0;
using (var peReader = new PEReader(new FileStream(dllPath, FileMode.Open), PEStreamOptions.Default))
{
var sections = peReader.PEHeaders.SectionHeaders;
for (int i = 0; i < sections.Length; i++)
{
ushort pdbSectionNumber = checked((ushort)(i+1));
_ngenWriter.AddSection(pdbSectionNumber, OMF.StandardText, 0, sections[i].SizeOfRawData);
if (sections[i].Name == ".text")
{
iCodeSection = pdbSectionNumber;
rvaOfTextSection = (uint)sections[i].VirtualAddress;
}
_ngenWriter.ModAddSecContribEx(_pdbMod, pdbSectionNumber, 0, sections[i].SizeOfRawData, (uint)sections[i].SectionCharacteristics, 0, 0);
}
}
// To support lines info, we need a "dummy" section, indexed as 0, for use as a
// sentinel when MSPDB sets up its section contribution table
_ngenWriter.AddSection(0, // Dummy section 0
OMF.SentinelType,
0,
unchecked((int)0xFFFFFFFF));
foreach (var method in methods)
{
WriteMethodPDBData(iCodeSection.Value, method, Path.GetFileNameWithoutExtension(originalDllPath), rvaOfTextSection, isILPDBProvided);
}
}
void WriteMethodPDBData(ushort iCodeSection, MethodInfo method, string assemblyName, uint textSectionOffset, bool isILPDBProvided)
{
string nameSuffix = $"{method.Name}$#{(assemblyName != method.AssemblyName ? method.AssemblyName : String.Empty)}#{method.MethodToken.ToString("X")}";
_ngenWriter.AddSymbol(nameSuffix, iCodeSection, method.HotRVA - textSectionOffset);
if (method.ColdRVA != 0)
{
_ngenWriter.AddSymbol($"[COLD] {nameSuffix}", iCodeSection, method.ColdRVA);
}
if (isILPDBProvided)
{
// line mapping not ported from crossgen yet.
throw new NotImplementedException();
}
}
private const int CV_SIGNATURE_C13 = 4;
private enum DEBUG_S_SUBSECTION_TYPE {
DEBUG_S_IGNORE = unchecked((int)0x80000000), // if this bit is set in a subsection type then ignore the subsection contents
DEBUG_S_SYMBOLS = 0xf1,
DEBUG_S_LINES,
DEBUG_S_STRINGTABLE,
DEBUG_S_FILECHKSMS,
DEBUG_S_FRAMEDATA,
DEBUG_S_INLINEELINES,
DEBUG_S_CROSSSCOPEIMPORTS,
DEBUG_S_CROSSSCOPEEXPORTS,
DEBUG_S_IL_LINES,
DEBUG_S_FUNC_MDTOKEN_MAP,
DEBUG_S_TYPE_MDTOKEN_MAP,
DEBUG_S_MERGED_ASSEMBLYINPUT,
DEBUG_S_COFF_SYMBOL_RVA,
}
private void WriteStringTable()
{
_stringTableToOffsetMapping = new Dictionary<string,int>();
MemoryStream stringTableStream = new MemoryStream();
BinaryWriter writer = new BinaryWriter(stringTableStream, Encoding.UTF8);
writer.Write(CV_SIGNATURE_C13);
writer.Write((uint)DEBUG_S_SUBSECTION_TYPE.DEBUG_S_STRINGTABLE);
long sizeOfStringTablePosition = writer.BaseStream.Position;
writer.Write((uint)0); // Size of actual string table. To be filled in later
long startOfStringTableOffset = writer.BaseStream.Position;
foreach (var document in _symDocuments)
{
string str = document.Name;
if (_stringTableToOffsetMapping.ContainsKey(str))
continue;
long offset = writer.BaseStream.Position;
_stringTableToOffsetMapping.Add(str, checked((int)(offset - startOfStringTableOffset)));
writer.Write(str.AsSpan());
writer.Write((byte)0); // Null terminate all strings
}
// Update string table size
long stringTableSize = writer.BaseStream.Position - startOfStringTableOffset;
writer.BaseStream.Position = sizeOfStringTablePosition;
writer.Write(checked((uint)stringTableSize));
writer.Flush();
// Write string table into pdb file
byte[] stringTableArray = stringTableStream.ToArray();
_ngenWriter.ModAddSymbols(_pdbMod, stringTableArray, stringTableArray.Length);
}
private void WriteFileChecksums()
{
_documentToChecksumOffsetMapping = new Dictionary<SymDocument,int>();
MemoryStream checksumStream = new MemoryStream();
BinaryWriter writer = new BinaryWriter(checksumStream, Encoding.UTF8);
writer.Write(CV_SIGNATURE_C13);
writer.Write((uint)DEBUG_S_SUBSECTION_TYPE.DEBUG_S_FILECHKSMS);
long sizeOfChecksumTablePosition = writer.BaseStream.Position;
writer.Write((uint)0); // Size of actual checksum table. To be filled in later
long startOfChecksumTableOffset = writer.BaseStream.Position;
foreach (var document in _symDocuments)
{
long offset = writer.BaseStream.Position;
_documentToChecksumOffsetMapping.Add(document, checked((int)(offset - startOfChecksumTableOffset)));
SymChecksumType checksumType = document.ChecksumType;
byte[] checksum = document.Checksum;
if (document.Checksum.Length > 255)
{
// Should never happen, but just in case checksum data is invalid, just put
// no checksum into the NGEN PDB
checksumType = SymChecksumType.None;
checksum = Array.Empty<byte>();
}
writer.Write(_stringTableToOffsetMapping[document.Name]);
writer.Write((byte)checksum.Length);
writer.Write((byte)checksumType);
writer.Write(checksum);
// Must align to the next 4-byte boundary
while ((writer.BaseStream.Position % 4) != 0)
{
writer.Write((byte)0);
}
}
// Update checksum table size
long checksumTableSize = writer.BaseStream.Position - startOfChecksumTableOffset;
writer.BaseStream.Position = sizeOfChecksumTablePosition;
writer.Write(checked((uint)checksumTableSize));
writer.Flush();
// Write string table into pdb file
byte[] checksumTableArray = checksumStream.ToArray();
_ngenWriter.ModAddSymbols(_pdbMod, checksumTableArray, checksumTableArray.Length);
}
}
}

View file

@ -15,6 +15,7 @@ using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using ILCompiler.Reflection.ReadyToRun;
using ILCompiler.PdbWriter;
using Internal.Runtime;
@ -46,6 +47,9 @@ namespace R2RDump
public bool DiffHideSameDisasm { get; set; }
public bool IgnoreSensitive { get; set; }
public bool CreatePDB { get; set; }
public string PdbPath { get; set; }
public FileInfo[] Reference { get; set; }
public DirectoryInfo[] ReferencePath { get; set; }
@ -337,8 +341,9 @@ namespace R2RDump
public void Dump(ReadyToRunReader r2r)
{
_dumper.Begin();
bool standardDump = !(_options.EntryPoints || _options.CreatePDB);
if (_options.Header || !_options.EntryPoints)
if (_options.Header && standardDump)
{
_dumper.WriteDivider("R2R Header");
_dumper.DumpHeader(true);
@ -377,7 +382,18 @@ namespace R2RDump
_dumper.DumpEntryPoints();
}
if (!_options.Header && !_options.EntryPoints)
if (_options.CreatePDB)
{
string pdbPath = _options.PdbPath;
if (String.IsNullOrEmpty(pdbPath))
{
pdbPath = Path.GetDirectoryName(r2r.Filename);
}
var pdbWriter = new PdbWriter(pdbPath, PDBExtraData.None);
pdbWriter.WritePDBData(r2r.Filename, ProducePdbWriterMethods(r2r));
}
if (!_options.Header && standardDump)
{
_dumper.DumpAllMethods();
}
@ -386,6 +402,21 @@ namespace R2RDump
_dumper.End();
}
IEnumerable<MethodInfo> ProducePdbWriterMethods(ReadyToRunReader r2r)
{
foreach (var method in _dumper.NormalizedMethods())
{
MethodInfo mi = new MethodInfo();
mi.Name = method.SignatureString;
mi.HotRVA = (uint)method.RuntimeFunctions[0].StartAddress;
mi.MethodToken = (uint)MetadataTokens.GetToken(method.MetadataReader, method.MethodHandle);
mi.AssemblyName = method.MetadataReader.GetString(method.MetadataReader.GetAssemblyDefinition().Name);
mi.ColdRVA = 0;
yield return mi;
}
}
/// <summary>
/// Returns true if the name, signature or id of <param>method</param> matches <param>query</param>
/// </summary>

View file

@ -15,6 +15,8 @@
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
<Import Project="$(RepositoryEngineeringDir)DiaSymReaderNative.targets" />
<ItemGroup>
<PackageReference Include="Microsoft.NETCore.CoreDisTools">
<Version>1.0.1-prerelease-00005</Version>