Initial support for R2R image diff (dotnet/coreclr#19533)
authorTomáš Rylek <trylek@microsoft.com>
Fri, 17 Aug 2018 23:41:22 +0000 (01:41 +0200)
committerGitHub <noreply@github.com>
Fri, 17 Aug 2018 23:41:22 +0000 (01:41 +0200)
* Initial support for R2R image diff

In this first cut I'm adding just three size diff statistics -
PE section size diff, R2R section size diff and method size diffs.
I assume we'll add more statistics as needed in the course of various
size investigations.

Thanks

Tomas

Commit migrated from https://github.com/dotnet/coreclr/commit/f43572eab2b9b560923435756aee09cb911f3296

src/coreclr/src/tools/r2rdump/R2RDiff.cs [new file with mode: 0644]
src/coreclr/src/tools/r2rdump/R2RDump.cs
src/coreclr/src/tools/r2rdump/R2RReader.cs

diff --git a/src/coreclr/src/tools/r2rdump/R2RDiff.cs b/src/coreclr/src/tools/r2rdump/R2RDiff.cs
new file mode 100644 (file)
index 0000000..a26e086
--- /dev/null
@@ -0,0 +1,207 @@
+// 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;
+        }
+    }
+}
index 9c57f71fa4fc2fefe82bae933b2d3e0e0962dbcb..6fa93b8fe9a8038d2295491278499dcb2b132db8 100644 (file)
@@ -102,7 +102,7 @@ namespace R2RDump
                 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");
             });
 
@@ -393,6 +393,11 @@ namespace R2RDump
                 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
@@ -412,8 +417,17 @@ namespace R2RDump
                         _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)
index 18f8b370a4df64818fa412f0f7dc0edce259b147..f0177cf5bd0390b9b7d3b44623ce3b73c629b8ba 100644 (file)
@@ -45,8 +45,16 @@ namespace R2RDump
 
     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
@@ -111,24 +119,24 @@ namespace R2RDump
             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)
@@ -136,9 +144,9 @@ namespace R2RDump
                     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))
@@ -200,7 +208,7 @@ namespace R2RDump
                     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)
                     {
@@ -249,7 +257,7 @@ namespace R2RDump
                     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;
@@ -333,7 +341,7 @@ namespace R2RDump
                 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();
             }
         }
@@ -440,12 +448,12 @@ namespace R2RDump
         /// <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;
         }