--- /dev/null
+// 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.Linq;
+using System.Reflection.PortableExecutable;
+using System.Text;
+
+namespace R2RDump
+{
+ /// <summary>
+ /// Helper class for diffing a pair of R2R images.
+ /// </summary>
+ class R2RDiff
+ {
+ /// <summary>
+ /// Left R2R image for the diff.
+ /// </summary>
+ private readonly R2RReader _leftFile;
+
+ /// <summary>
+ /// Right R2R image for the diff.
+ /// </summary>
+ private readonly R2RReader _rightFile;
+
+ /// <summary>
+ /// Text writer to receive diff output.
+ /// </summary>
+ private readonly TextWriter _writer;
+
+ /// <summary>
+ /// Store the left and right file and output writer.
+ /// </summary>
+ /// <param name="leftFile">Left R2R file</param>
+ /// <param name="rightFile">Right R2R file</param>
+ /// <param name="writer">Output writer to receive the diff</param>
+ public R2RDiff(R2RReader leftFile, R2RReader rightFile, TextWriter writer)
+ {
+ _leftFile = leftFile;
+ _rightFile = rightFile;
+ _writer = writer;
+ }
+
+ /// <summary>
+ /// Public API runs all available diff algorithms in sequence.
+ /// </summary>
+ public void Run()
+ {
+ DiffTitle();
+ DiffPESections();
+ DiffR2RSections();
+ DiffR2RMethods();
+ }
+
+ /// <summary>
+ /// Diff title shows the names of the files being compared and their lengths.
+ /// </summary>
+ private void DiffTitle()
+ {
+ _writer.WriteLine($@"Left file: {_leftFile.Filename} ({_leftFile.Image.Length} B)");
+ _writer.WriteLine($@"Right file: {_rightFile.Filename} ({_rightFile.Image.Length} B)");
+ _writer.WriteLine();
+ }
+
+ /// <summary>
+ /// Diff raw PE sections.
+ /// </summary>
+ private void DiffPESections()
+ {
+ ShowDiff(GetPESectionMap(_leftFile), GetPESectionMap(_rightFile), "PE sections");
+ }
+
+ /// <summary>
+ /// Diff R2R header sections.
+ /// </summary>
+ private void DiffR2RSections()
+ {
+ ShowDiff(GetR2RSectionMap(_leftFile), GetR2RSectionMap(_rightFile), "R2R sections");
+ }
+
+ /// <summary>
+ /// Diff the R2R method maps.
+ /// </summary>
+ private void DiffR2RMethods()
+ {
+ ShowDiff(GetR2RMethodMap(_leftFile), GetR2RMethodMap(_rightFile), "R2R methods");
+ }
+
+ /// <summary>
+ /// Show a difference summary between the sets of "left objects" and "right objects".
+ /// </summary>
+ /// <param name="leftObjects">Dictionary of left object sizes keyed by their names</param>
+ /// <param name="rightObjects">Dictionary of right object sizes keyed by their names</param>
+ /// <param name="diffName">Logical name of the diffing operation to display in the header line</param>
+ private void ShowDiff(Dictionary<string, int> leftObjects, Dictionary<string, int> rightObjects, string diffName)
+ {
+ HashSet<string> allKeys = new HashSet<string>(leftObjects.Keys);
+ allKeys.UnionWith(rightObjects.Keys);
+
+ string title = $@" LEFT_SIZE RIGHT_SIZE DIFF {diffName} ({allKeys.Count} ELEMENTS)";
+
+ _writer.WriteLine(title);
+ _writer.WriteLine(new string('-', title.Length));
+
+ int leftTotal = 0;
+ int rightTotal = 0;
+ foreach (string key in allKeys)
+ {
+ int leftSize;
+ bool inLeft = leftObjects.TryGetValue(key, out leftSize);
+ int rightSize;
+ bool inRight = rightObjects.TryGetValue(key, out rightSize);
+
+ leftTotal += leftSize;
+ rightTotal += rightSize;
+
+ StringBuilder line = new StringBuilder();
+ if (inLeft)
+ {
+ line.AppendFormat("{0,10}", leftSize);
+ }
+ else
+ {
+ line.Append(' ', 10);
+ }
+ if (inRight)
+ {
+ line.AppendFormat("{0,11}", rightSize);
+ }
+ else
+ {
+ line.Append(' ', 11);
+ }
+ if (leftSize != rightSize)
+ {
+ line.AppendFormat("{0,11}", rightSize - leftSize);
+ }
+ else
+ {
+ line.Append(' ', 11);
+ }
+ line.Append(" ");
+ line.Append(key);
+ _writer.WriteLine(line);
+ }
+ _writer.WriteLine($@"{leftTotal,10} {rightTotal,10} {(rightTotal - leftTotal),10} <TOTAL>");
+
+ _writer.WriteLine();
+ }
+
+ /// <summary>
+ /// Read the PE file section map for a given R2R image.
+ /// </summary>
+ /// <param name="reader">R2R image to scan</param>
+ /// <returns></returns>
+ private Dictionary<string, int> GetPESectionMap(R2RReader reader)
+ {
+ Dictionary<string, int> sectionMap = new Dictionary<string, int>();
+
+ foreach (SectionHeader sectionHeader in reader.PEReader.PEHeaders.SectionHeaders)
+ {
+ sectionMap.Add(sectionHeader.Name, sectionHeader.SizeOfRawData);
+ }
+
+ return sectionMap;
+ }
+
+ /// <summary>
+ /// Read the R2R header section map for a given R2R image.
+ /// </summary>
+ /// <param name="reader">R2R image to scan</param>
+ /// <returns></returns>
+ private Dictionary<string, int> GetR2RSectionMap(R2RReader reader)
+ {
+ Dictionary<string, int> sectionMap = new Dictionary<string, int>();
+
+ foreach (KeyValuePair<R2RSection.SectionType, R2RSection> typeAndSection in reader.R2RHeader.Sections)
+ {
+ string name = typeAndSection.Key.ToString();
+ sectionMap.Add(name, typeAndSection.Value.Size);
+ }
+
+ return sectionMap;
+ }
+
+ /// <summary>
+ /// Read the R2R method map for a given R2R image.
+ /// </summary>
+ /// <param name="reader">R2R image to scan</param>
+ /// <returns></returns>
+ private Dictionary<string, int> GetR2RMethodMap(R2RReader reader)
+ {
+ Dictionary<string, int> methodMap = new Dictionary<string, int>();
+
+ foreach (R2RMethod method in reader.R2RMethods)
+ {
+ int size = method.RuntimeFunctions.Sum(rf => rf.Size);
+ methodMap.Add(method.SignatureString, size);
+ }
+
+ return methodMap;
+ }
+ }
+}
syntax.DefineOption("gc", ref _gc, "Dump gcInfo and slot table");
syntax.DefineOption("sc", ref _sectionContents, "Dump section contents");
syntax.DefineOption("v|verbose", ref verbose, "Dump raw bytes, disassembly, unwindInfo, gcInfo and section contents");
- syntax.DefineOption("diff", ref _diff, "Compare two R2R images (not yet implemented)");
+ syntax.DefineOption("diff", ref _diff, "Compare two R2R images");
syntax.DefineOption("ignoreSensitive", ref _ignoreSensitive, "Ignores sensitive properties in xml dump to avoid failing tests");
});
if (_inputFilenames.Count == 0)
throw new ArgumentException("Input filename must be specified (--in <file>)");
+ if (_diff && _inputFilenames.Count < 2)
+ throw new ArgumentException("Need at least 2 input files in diff mode");
+
+ R2RReader previousReader = null;
+
foreach (string filename in _inputFilenames)
{
// parse the ReadyToRun image
_dumper = new TextDumper(r2r, _writer, _raw, _header, _disasm, disassembler, _unwind, _gc, _sectionContents);
}
- // output the ReadyToRun info
- Dump(r2r);
+ if (!_diff)
+ {
+ // output the ReadyToRun info
+ Dump(r2r);
+ }
+ else if (previousReader != null)
+ {
+ new R2RDiff(previousReader, r2r, _writer).Run();
+ }
+
+ previousReader = r2r;
}
}
catch (Exception e)
public class R2RReader
{
- private readonly PEReader _peReader;
- private readonly MetadataReader _mdReader;
+ /// <summary>
+ /// Underlying PE image reader is used to access raw PE structures like header
+ /// or section list.
+ /// </summary>
+ public readonly PEReader PEReader;
+
+ /// <summary>
+ /// MetadataReader is used to access the MSIL metadata in the R2R file.
+ /// </summary>
+ public readonly MetadataReader MetadataReader;
/// <summary>
/// Byte array containing the ReadyToRun image
fixed (byte* p = Image)
{
IntPtr ptr = (IntPtr)p;
- _peReader = new PEReader(p, Image.Length);
+ PEReader = new PEReader(p, Image.Length);
- IsR2R = ((_peReader.PEHeaders.CorHeader.Flags & CorFlags.ILLibrary) != 0);
+ IsR2R = ((PEReader.PEHeaders.CorHeader.Flags & CorFlags.ILLibrary) != 0);
if (!IsR2R)
{
throw new BadImageFormatException("The file is not a ReadyToRun image");
}
- Machine = _peReader.PEHeaders.CoffHeader.Machine;
+ Machine = PEReader.PEHeaders.CoffHeader.Machine;
if (!Machine.IsDefined(typeof(Machine), Machine))
{
Machine = Machine.Amd64;
R2RDump.WriteWarning($"Invalid Machine: {Machine}");
}
- ImageBase = _peReader.PEHeaders.PEHeader.ImageBase;
+ ImageBase = PEReader.PEHeaders.PEHeader.ImageBase;
// initialize R2RHeader
- DirectoryEntry r2rHeaderDirectory = _peReader.PEHeaders.CorHeader.ManagedNativeHeaderDirectory;
+ DirectoryEntry r2rHeaderDirectory = PEReader.PEHeaders.CorHeader.ManagedNativeHeaderDirectory;
int r2rHeaderOffset = GetOffset(r2rHeaderDirectory.RelativeVirtualAddress);
R2RHeader = new R2RHeader(Image, r2rHeaderDirectory.RelativeVirtualAddress, r2rHeaderOffset);
if (r2rHeaderDirectory.Size != R2RHeader.Size)
throw new BadImageFormatException("The calculated size of the R2RHeader doesn't match the size saved in the ManagedNativeHeaderDirectory");
}
- if (_peReader.HasMetadata)
+ if (PEReader.HasMetadata)
{
- _mdReader = _peReader.GetMetadataReader();
+ MetadataReader = PEReader.GetMetadataReader();
R2RMethods = new List<R2RMethod>();
if (R2RHeader.Sections.ContainsKey(R2RSection.SectionType.READYTORUN_SECTION_RUNTIME_FUNCTIONS))
int runtimeFunctionId;
FixupCell[] fixups;
GetRuntimeFunctionIndexFromOffset(offset, out runtimeFunctionId, out fixups);
- R2RMethod method = new R2RMethod(R2RMethods.Count, _mdReader, rid, runtimeFunctionId, null, null, fixups);
+ R2RMethod method = new R2RMethod(R2RMethods.Count, MetadataReader, rid, runtimeFunctionId, null, null, fixups);
if (method.EntryPointRuntimeFunctionId < 0 || method.EntryPointRuntimeFunctionId >= isEntryPoint.Length)
{
int runtimeFunctionId;
FixupCell[] fixups;
GetRuntimeFunctionIndexFromOffset((int)curParser.Offset, out runtimeFunctionId, out fixups);
- R2RMethod method = new R2RMethod(R2RMethods.Count, _mdReader, rid, runtimeFunctionId, args, tokens, fixups);
+ R2RMethod method = new R2RMethod(R2RMethods.Count, MetadataReader, rid, runtimeFunctionId, args, tokens, fixups);
if (method.EntryPointRuntimeFunctionId >= 0 && method.EntryPointRuntimeFunctionId < isEntryPoint.Length)
{
isEntryPoint[method.EntryPointRuntimeFunctionId] = true;
uint rid = curParser.GetUnsigned();
rid = rid >> 1;
TypeDefinitionHandle typeDefHandle = MetadataTokens.TypeDefinitionHandle((int)rid);
- AvailableTypes.Add(GetTypeDefFullName(_mdReader, typeDefHandle));
+ AvailableTypes.Add(GetTypeDefFullName(MetadataReader, typeDefHandle));
curParser = allEntriesEnum.GetNext();
}
}
/// <param name="rva">The relative virtual address</param>
public int GetOffset(int rva)
{
- int index = _peReader.PEHeaders.GetContainingSectionIndex(rva);
+ int index = PEReader.PEHeaders.GetContainingSectionIndex(rva);
if (index == -1)
{
throw new BadImageFormatException("Failed to convert invalid RVA to offset: " + rva);
}
- SectionHeader containingSection = _peReader.PEHeaders.SectionHeaders[index];
+ SectionHeader containingSection = PEReader.PEHeaders.SectionHeaders[index];
return rva - containingSection.VirtualAddress + containingSection.PointerToRawData;
}