Skip to content

Commit

Permalink
Emit DebugDirectory section and all DebugDirectory entries (#2398)
Browse files Browse the repository at this point in the history
* Emit DebugDirectory section and all DebugDirectory entries

- Add new RELOC type for raw data points
- Emit native DebugDirectory entry with proper MD5 hash of the output image
- Fix determinism issue: timestamp has to be copied from input IL image
  • Loading branch information
Fadi Hanna authored Feb 1, 2020
1 parent 483d042 commit 6c8a629
Show file tree
Hide file tree
Showing 13 changed files with 434 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ public void EmitReloc(ISymbolNode symbol, RelocType relocType, int delta = 0)
case RelocType.IMAGE_REL_BASED_ABSOLUTE:
case RelocType.IMAGE_REL_BASED_HIGHLOW:
case RelocType.IMAGE_REL_SECREL:
case RelocType.IMAGE_REL_FILE_ABSOLUTE:
case RelocType.IMAGE_REL_BASED_ADDR32NB:
case RelocType.IMAGE_REL_SYMBOL_SIZE:
EmitInt(delta);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ public enum RelocType
//
// Relocations for R2R image production
//
IMAGE_REL_SYMBOL_SIZE = 0x1000, // The size of data in the image represented by the target symbol node
IMAGE_REL_SYMBOL_SIZE = 0x1000, // The size of data in the image represented by the target symbol node
IMAGE_REL_FILE_ABSOLUTE = 0x1001, // 32 bit offset from begining of image
}

public struct Relocation
Expand Down Expand Up @@ -272,6 +273,7 @@ public static unsafe void WriteValue(RelocType relocType, void* location, long v
case RelocType.IMAGE_REL_BASED_REL32:
case RelocType.IMAGE_REL_BASED_ADDR32NB:
case RelocType.IMAGE_REL_SYMBOL_SIZE:
case RelocType.IMAGE_REL_FILE_ABSOLUTE:
*(int*)location = (int)value;
break;
case RelocType.IMAGE_REL_BASED_DIR64:
Expand Down Expand Up @@ -305,6 +307,7 @@ public static unsafe long ReadValue(RelocType relocType, void* location)
case RelocType.IMAGE_REL_BASED_REL32:
case RelocType.IMAGE_REL_BASED_RELPTR32:
case RelocType.IMAGE_REL_SECREL:
case RelocType.IMAGE_REL_FILE_ABSOLUTE:
case RelocType.IMAGE_REL_SYMBOL_SIZE:
return *(int*)location;
case RelocType.IMAGE_REL_BASED_DIR64:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using ObjectData = ILCompiler.DependencyAnalysis.ObjectNode.ObjectData;

using Internal.TypeSystem;
using System.Security.Cryptography;

namespace ILCompiler.DependencyAnalysis
{
Expand Down Expand Up @@ -83,6 +84,8 @@ public void EmitPortableExecutable()
_inputPeReader,
GetRuntimeFunctionsTable);

NativeDebugDirectoryEntryNode nativeDebugDirectoryEntryNode = null;

int nodeIndex = -1;
foreach (var depNode in _nodes)
{
Expand All @@ -99,6 +102,15 @@ public void EmitPortableExecutable()

ObjectData nodeContents = node.GetData(_nodeFactory);

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;
}

string name = null;

if (mapFile != null)
Expand All @@ -120,6 +132,7 @@ public void EmitPortableExecutable()
}

r2rPeBuilder.SetCorHeader(_nodeFactory.CopiedCorHeaderNode, _nodeFactory.CopiedCorHeaderNode.Size);
r2rPeBuilder.SetDebugDirectory(_nodeFactory.DebugDirectoryNode, _nodeFactory.DebugDirectoryNode.Size);

if (_nodeFactory.Win32ResourcesNode != null)
{
Expand All @@ -130,6 +143,18 @@ public void EmitPortableExecutable()
using (var peStream = File.Create(_objectFilePath))
{
r2rPeBuilder.Write(peStream);

// Compute MD5 hash of the output image and store that in the native DebugDirectory entry
using (var md5Hash = MD5.Create())
{
peStream.Seek(0, SeekOrigin.Begin);
byte[] hash = md5Hash.ComputeHash(peStream);
byte[] rsdsEntry = nativeDebugDirectoryEntryNode.GenerateRSDSEntryData(hash);

int offsetToUpdate = r2rPeBuilder.GetSymbolFilePosition(nativeDebugDirectoryEntryNode);
peStream.Seek(offsetToUpdate, SeekOrigin.Begin);
peStream.Write(rsdsEntry);
}
}

if (mapFile != null)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// 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.Text;
using System.Diagnostics;
using System.Reflection.PortableExecutable;

using Internal.Text;
using Internal.TypeSystem.Ecma;
using System.IO;
using System.Collections.Immutable;

namespace ILCompiler.DependencyAnalysis.ReadyToRun
{
public abstract class DebugDirectoryEntryNode : ObjectNode, ISymbolDefinitionNode
{
protected readonly EcmaModule _module;

public DebugDirectoryEntryNode(EcmaModule module)
{
_module = module;
}

public override ObjectNodeSection Section => ObjectNodeSection.TextSection;

public override bool IsShareable => false;

protected internal override int Phase => (int)ObjectNodePhase.Ordered;

public override bool StaticDependenciesAreComputed => true;

public int Offset => 0;

public abstract void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb);

protected override string GetName(NodeFactory factory) => this.GetMangledName(factory.NameMangler);

public override int CompareToImpl(ISortableNode other, CompilerComparer comparer)
{
return _module.CompareTo(((DebugDirectoryEntryNode)other)._module);
}
}

public class NativeDebugDirectoryEntryNode : DebugDirectoryEntryNode
{
const int RSDSSize =
sizeof(int) + // Magic
16 + // Signature (guid)
sizeof(int) + // Age
260; // FileName

public override int ClassCode => 119958401;

public unsafe int Size => RSDSSize;

public NativeDebugDirectoryEntryNode(EcmaModule sourceModule)
: base(sourceModule)
{ }

public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
{
sb.Append(nameMangler.CompilationUnitPrefix);
sb.Append($"__NativeRvaBlob_{_module.Assembly.GetName().Name}");
}

public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
{
ObjectDataBuilder builder = new ObjectDataBuilder(factory, relocsOnly);
builder.RequireInitialPointerAlignment();
builder.AddSymbol(this);

// Emit empty entry. This will be filled with data after the output image is emitted
builder.EmitZeros(RSDSSize);

return builder.ToObjectData();
}

public byte[] GenerateRSDSEntryData(byte[] md5Hash)
{
MemoryStream rsdsEntry = new MemoryStream(RSDSSize);

using (BinaryWriter writer = new BinaryWriter(rsdsEntry))
{
// Magic "RSDS"
writer.Write((uint)0x53445352);

// The PDB signature will be the same as our NGEN signature.
// However we want the printed version of the GUID to be the same as the
// byte dump of the signature so we swap bytes to make this work.
Debug.Assert(md5Hash.Length == 16);
writer.Write((uint)((md5Hash[0] * 256 + md5Hash[1]) * 256 + md5Hash[2]) * 256 + md5Hash[3]);
writer.Write((ushort)(md5Hash[4] * 256 + md5Hash[5]));
writer.Write((ushort)(md5Hash[6] * 256 + md5Hash[7]));
writer.Write(md5Hash, 8, 8);

// Age
writer.Write(1);

string pdbFileName = _module.Assembly.GetName().Name + ".ni.pdb";
byte[] pdbFileNameBytes = Encoding.UTF8.GetBytes(pdbFileName);
writer.Write(pdbFileNameBytes);

Debug.Assert(rsdsEntry.Length <= RSDSSize);
return rsdsEntry.ToArray();
}
}
}

public class CopiedDebugDirectoryEntryNode : DebugDirectoryEntryNode
{
private readonly int _debugEntryIndex;

public override int ClassCode => 1558397;

public CopiedDebugDirectoryEntryNode(EcmaModule sourceModule, int debugEntryIndex)
: base(sourceModule)
{
Debug.Assert(debugEntryIndex >= 0);
_debugEntryIndex = debugEntryIndex;
}

public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
{
sb.Append(nameMangler.CompilationUnitPrefix);
sb.Append($"__CopiedDebugEntryNode_{_debugEntryIndex}_{_module.Assembly.GetName().Name}");
}

public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
{
if (relocsOnly)
{
return new ObjectData(Array.Empty<byte>(), Array.Empty<Relocation>(), 1, new ISymbolDefinitionNode[] { this });
}

ImmutableArray<DebugDirectoryEntry> entries = _module.PEReader.ReadDebugDirectory();
Debug.Assert(entries != null && _debugEntryIndex < entries.Length);

DebugDirectoryEntry sourceDebugEntry = entries[_debugEntryIndex];

PEMemoryBlock block = _module.PEReader.GetSectionData(sourceDebugEntry.DataRelativeVirtualAddress);
byte[] result = new byte[sourceDebugEntry.DataSize];
block.GetContent(0, sourceDebugEntry.DataSize).CopyTo(result);

return new ObjectData(result, Array.Empty<Relocation>(), _module.Context.Target.PointerSize, new ISymbolDefinitionNode[] { this });
}

public override int CompareToImpl(ISortableNode other, CompilerComparer comparer)
{
int moduleComp = base.CompareToImpl(other, comparer);
if (moduleComp != 0)
return moduleComp;

return _debugEntryIndex - ((CopiedDebugDirectoryEntryNode)other)._debugEntryIndex;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// 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.Immutable;
using System.Diagnostics;
using System.Reflection.PortableExecutable;
using Internal.Text;
using Internal.TypeSystem.Ecma;

namespace ILCompiler.DependencyAnalysis.ReadyToRun
{
public class DebugDirectoryNode : ObjectNode, ISymbolDefinitionNode
{
const int ImageDebugDirectorySize =
sizeof(int) + // Characteristics
sizeof(int) + // TimeDateStamp
sizeof(short) + // MajorVersion:
sizeof(short) + // MinorVersion
sizeof(int) + // Type
sizeof(int) + // SizeOfData:
sizeof(int) + // AddressOfRawData:
sizeof(int); // PointerToRawData

private EcmaModule _module;

public DebugDirectoryNode(EcmaModule sourceModule)
{
_module = sourceModule;
}

public override ObjectNodeSection Section => ObjectNodeSection.TextSection;

public override bool IsShareable => false;

protected internal override int Phase => (int)ObjectNodePhase.Ordered;

public override int ClassCode => 315358387;

public override bool StaticDependenciesAreComputed => true;

public int Offset => 0;

public int Size => (GetNumDebugDirectoryEntriesInModule() + 1) * ImageDebugDirectorySize;

public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
{
sb.Append(nameMangler.CompilationUnitPrefix);
sb.Append($"__DebugDirectory_{_module.Assembly.GetName().Name}");
}

protected override string GetName(NodeFactory factory) => this.GetMangledName(factory.NameMangler);

int GetNumDebugDirectoryEntriesInModule()
{
ImmutableArray<DebugDirectoryEntry> entries = _module.PEReader.ReadDebugDirectory();
return entries == null ? 0 : entries.Length;
}

public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
{
ObjectDataBuilder builder = new ObjectDataBuilder(factory, relocsOnly);
builder.RequireInitialPointerAlignment();
builder.AddSymbol(this);

ImmutableArray<DebugDirectoryEntry> entries = _module.PEReader.ReadDebugDirectory();
int numEntries = GetNumDebugDirectoryEntriesInModule();

// First, write the native debug directory entry
{
var entry = (NativeDebugDirectoryEntryNode)factory.DebugDirectoryEntry(_module, -1);

builder.EmitUInt(0 /* Characteristics */);
if (numEntries > 0)
{
builder.EmitUInt(entries[0].Stamp);
builder.EmitUShort(entries[0].MajorVersion);
}
else
{
builder.EmitUInt(0);
builder.EmitUShort(0);
}
// Make sure the "is portable pdb" indicator (MinorVersion == 0x504d) is clear
// for the NGen debug directory entry since this debug directory can be copied
// from an existing entry which could be a portable pdb.
builder.EmitUShort(0 /* MinorVersion */);
builder.EmitInt((int)DebugDirectoryEntryType.CodeView);
builder.EmitInt(entry.Size);
builder.EmitReloc(entry, RelocType.IMAGE_REL_BASED_ADDR32NB);
builder.EmitReloc(entry, RelocType.IMAGE_REL_FILE_ABSOLUTE);
}

// Second, copy existing entries from input module
for(int i = 0; i < numEntries; i++)
{
builder.EmitUInt(0 /* Characteristics */);
builder.EmitUInt(entries[i].Stamp);
builder.EmitUShort(entries[i].MajorVersion);
builder.EmitUShort(entries[i].MinorVersion);
builder.EmitInt((int)entries[i].Type);
builder.EmitInt(entries[i].DataSize);
if (entries[i].DataSize == 0)
{
builder.EmitUInt(0);
builder.EmitUInt(0);
}
else
{
builder.EmitReloc(factory.DebugDirectoryEntry(_module, i), RelocType.IMAGE_REL_BASED_ADDR32NB);
builder.EmitReloc(factory.DebugDirectoryEntry(_module, i), RelocType.IMAGE_REL_FILE_ABSOLUTE);
}
}

Debug.Assert(builder.CountBytes == Size);

return builder.ToObjectData();
}

public override int CompareToImpl(ISortableNode other, CompilerComparer comparer)
{
return _module.CompareTo(((DebugDirectoryNode)other)._module);
}
}
}
Loading

0 comments on commit 6c8a629

Please sign in to comment.