R2RDump - Print ReadyToRun image headers (dotnet/coreclr#17942)
authoracmyu <amycmyu@gmail.com>
Mon, 14 May 2018 17:23:07 +0000 (10:23 -0700)
committerZach Montoya <zamont@microsoft.com>
Mon, 14 May 2018 17:23:06 +0000 (10:23 -0700)
* R2RDump - Outputing ReadyToRun header from PE image

* R2RDump - Comments, follow coding guidelines, use enum for section types and flags

* R2RDump - Use utf8 encoding, rva from ManagedNativeHeader, return exit code

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

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

diff --git a/src/coreclr/src/tools/r2rdump/R2RDump.cs b/src/coreclr/src/tools/r2rdump/R2RDump.cs
new file mode 100644 (file)
index 0000000..07d4bbc
--- /dev/null
@@ -0,0 +1,45 @@
+// 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;
+
+namespace R2RDump
+{
+    class R2RDump
+    {
+        public static int Main(string[] args)
+        {
+            try
+            {
+                if (args.Length < 1)
+                {
+                    throw new System.ArgumentException("File name must be passed as argument");
+                }
+
+                R2RReader r2r = new R2RReader(args[0]);
+
+                if (r2r.IsR2R)
+                {
+                    Console.WriteLine($"Filename: {r2r.Filename}");
+                    Console.WriteLine($"Machine: {r2r.Machine}");
+                    Console.WriteLine($"ImageBase: 0x{r2r.ImageBase:X8}");
+
+                    Console.WriteLine("============== R2R Header ==============");
+                    Console.WriteLine(r2r.R2RHeader.ToString());
+                    for (int i = 0; i < r2r.R2RHeader.NumberOfSections; i++)
+                    {
+                        Console.WriteLine("------------------");
+                        Console.WriteLine(r2r.R2RHeader.Sections[i].ToString());
+                    }
+                }
+            }
+            catch (Exception e)
+            {
+                Console.WriteLine("Error: " + e.ToString());
+                return 1;
+            }
+            return 0;
+        }
+    }
+}
diff --git a/src/coreclr/src/tools/r2rdump/R2RDump.csproj b/src/coreclr/src/tools/r2rdump/R2RDump.csproj
new file mode 100644 (file)
index 0000000..b9b5b5c
--- /dev/null
@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>netcoreapp2.0</TargetFramework>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+  </PropertyGroup>
+
+</Project>
diff --git a/src/coreclr/src/tools/r2rdump/R2RHeader.cs b/src/coreclr/src/tools/r2rdump/R2RHeader.cs
new file mode 100644 (file)
index 0000000..febcc53
--- /dev/null
@@ -0,0 +1,158 @@
+// 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;
+
+namespace R2RDump
+{
+    class R2RHeader
+    {
+        public enum ReadyToRunFlag
+        {
+            READYTORUN_FLAG_PLATFORM_NEUTRAL_SOURCE = 0x00000001,
+            READYTORUN_FLAG_SKIP_TYPE_VALIDATION = 0x00000002
+        }
+
+        /// <summary>
+        /// The expected signature of a ReadyToRun header
+        /// </summary>
+        public const uint READYTORUN_SIGNATURE = 0x00525452; // 'RTR'
+
+        /// <summary>
+        /// RVA to the begining of the ReadyToRun header
+        /// </summary>
+        public uint RelativeVirtualAddress { get; }
+
+        /// <summary>
+        /// Index in the image byte array to the start of the ReadyToRun header
+        /// </summary>
+        public int Offset { get; }
+
+        /// <summary>
+        /// Size of the ReadyToRun header
+        /// </summary>
+        public int Size { get; }
+
+        /// <summary>
+        /// Signature of the header in string and hex formats
+        /// </summary>
+        public string SignatureString { get; }
+        public uint Signature { get; }
+
+        /// <summary>
+        /// The ReadyToRun version
+        /// </summary>
+        public ushort MajorVersion { get; }
+        public ushort MinorVersion { get; }
+
+        /// <summary>
+        /// Flags in the header
+        /// eg. PLATFORM_NEUTRAL_SOURCE, SKIP_TYPE_VALIDATION
+        /// </summary>
+        public uint Flags { get; }
+
+        /// <summary>
+        /// The ReadyToRun sections
+        /// </summary>
+        public uint NumberOfSections { get; }
+        public R2RSection[] Sections { get; }
+
+        /// <summary>
+        /// Initializes the fields of the R2RHeader
+        /// </summary>
+        /// <param name="image">PE image</param>
+        /// <param name="rva">Relative virtual address of the ReadyToRun header</param>
+        /// <param name="curOffset">Index in the image byte array to the start of the ReadyToRun header</param>
+        /// <exception cref="BadImageFormatException">The signature must be 0x00525452</exception>
+        public R2RHeader(byte[] image, uint rva, int curOffset)
+        {
+            RelativeVirtualAddress = rva;
+            Offset = curOffset;
+
+            byte[] signature = new byte[sizeof(uint)];
+            Array.Copy(image, curOffset, signature, 0, sizeof(uint));
+            SignatureString = System.Text.Encoding.UTF8.GetString(signature);
+            Signature = (uint)GetField(image, ref curOffset, sizeof(uint));
+            if (Signature != READYTORUN_SIGNATURE)
+            {
+                throw new System.BadImageFormatException("Incorrect R2R header signature");
+            }
+
+            MajorVersion = (ushort)GetField(image, ref curOffset, sizeof(ushort));
+            MinorVersion = (ushort)GetField(image, ref curOffset, sizeof(ushort));
+            Flags = (uint)GetField(image, ref curOffset, sizeof(uint));
+            NumberOfSections = (uint)GetField(image, ref curOffset, sizeof(uint));
+
+            Sections = new R2RSection[NumberOfSections];
+            for (int i = 0; i < NumberOfSections; i++)
+            {
+                Sections[i] = new R2RSection((int)GetField(image, ref curOffset, sizeof(int)),
+                    (uint)GetField(image, ref curOffset, sizeof(uint)),
+                    (uint)GetField(image, ref curOffset, sizeof(uint)));
+            }
+
+            Size = curOffset - Offset;
+        }
+
+        public override string ToString()
+        {
+            StringBuilder sb = new StringBuilder();
+            sb.AppendFormat($"Signature: 0x{Signature:X8} ({SignatureString})\n");
+            sb.AppendFormat($"RelativeVirtualAddress: 0x{RelativeVirtualAddress:X8}\n");
+            if (Signature == READYTORUN_SIGNATURE)
+            {
+                sb.AppendFormat($"Size: {Size} bytes\n");
+                sb.AppendFormat($"MajorVersion: 0x{MajorVersion:X4}\n");
+                sb.AppendFormat($"MinorVersion: 0x{MinorVersion:X4}\n");
+                sb.AppendFormat($"Flags: 0x{Flags:X8}\n");
+                foreach (ReadyToRunFlag flag in Enum.GetValues(typeof(ReadyToRunFlag)))
+                {
+                    if ((Flags & (uint)flag) != 0)
+                    {
+                        sb.AppendFormat($"  - {Enum.GetName(typeof(ReadyToRunFlag), flag)}\n");
+                    }
+                }
+                sb.AppendFormat($"NumberOfSections: {NumberOfSections}\n");
+            }
+            return sb.ToString();
+        }
+
+        /// <summary>
+        /// Extracts a value from the image byte array
+        /// </summary>
+        /// <param name="image">PE image</param>
+        /// <param name="start">Starting index of the value</param>
+        /// <param name="size">Size of the value in bytes</param>
+        /// <exception cref="ArgumentException"><paramref name="size"/> is not 8, 4 or 2</exception>
+        public long GetField(byte[] image, int start, int size)
+        {
+            return GetField(image, ref start, size);
+        }
+
+        /// <remarks>
+        /// The <paramref name="start"/> gets incremented to the end of the value
+        /// </remarks>
+        public long GetField(byte[] image, ref int start, int size)
+        {
+            byte[] bytes = new byte[size];
+            Array.Copy(image, start, bytes, 0, size);
+            start += size;
+
+            if (size == 8)
+            {
+                return BitConverter.ToInt64(bytes, 0);
+            }
+            else if (size == 4)
+            {
+                return BitConverter.ToInt32(bytes, 0);
+            }
+            else if (size == 2)
+            {
+                return BitConverter.ToInt16(bytes, 0);
+            }
+            throw new System.ArgumentException("Invalid field size");
+        }
+    }
+}
diff --git a/src/coreclr/src/tools/r2rdump/R2RReader.cs b/src/coreclr/src/tools/r2rdump/R2RReader.cs
new file mode 100644 (file)
index 0000000..f9aa63e
--- /dev/null
@@ -0,0 +1,89 @@
+// 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.IO;
+using System.Reflection.PortableExecutable;
+
+namespace R2RDump
+{
+    class R2RReader
+    {
+        /// <summary>
+        /// Byte array containing the ReadyToRun image
+        /// </summary>
+        private readonly byte[] _image;
+
+        /// <summary>
+        /// Name of the image file
+        /// </summary>
+        public string Filename { get; }
+
+        /// <summary>
+        /// True if the image is ReadyToRun
+        /// </summary>
+        public bool IsR2R { get; }
+
+        /// <summary>
+        /// The type of target machine
+        /// </summary>
+        public Machine Machine { get; }
+
+        /// <summary>
+        /// The preferred address of the first byte of image when loaded into memory; 
+        /// must be a multiple of 64K.
+        /// </summary>
+        public ulong ImageBase { get; }
+
+        /// <summary>
+        /// The ReadyToRun header
+        /// </summary>
+        public R2RHeader R2RHeader { get; }
+
+        /// <summary>
+        /// Initializes the fields of the R2RHeader
+        /// </summary>
+        /// <param name="filename">PE image</param>
+        /// <exception cref="BadImageFormatException">The Cor header flag must be ILLibrary</exception>
+        public unsafe R2RReader(string filename)
+        {
+            Filename = filename;
+            _image = File.ReadAllBytes(filename);
+
+            fixed (byte* p = _image)
+            {
+                IntPtr ptr = (IntPtr)p;
+                PEReader peReader = new PEReader(p, _image.Length);
+
+                IsR2R = (peReader.PEHeaders.CorHeader.Flags == CorFlags.ILLibrary);
+                if (!IsR2R)
+                {
+                    throw new System.BadImageFormatException("The file is not a ReadyToRun image");
+                }
+
+                Machine = peReader.PEHeaders.CoffHeader.Machine;
+                ImageBase = peReader.PEHeaders.PEHeader.ImageBase;
+
+                SectionHeader textSection;
+                int nSections = peReader.PEHeaders.CoffHeader.NumberOfSections;
+                for (int i = 0; i < nSections; i++)
+                {
+                    SectionHeader section = peReader.PEHeaders.SectionHeaders[i];
+                    if (section.Name.Equals(".text"))
+                    {
+                        textSection = section;
+                    }
+                }
+
+                DirectoryEntry r2rHeaderDirectory = peReader.PEHeaders.CorHeader.ManagedNativeHeaderDirectory;
+                int r2rHeaderOffset = r2rHeaderDirectory.RelativeVirtualAddress - textSection.VirtualAddress + textSection.PointerToRawData;
+                R2RHeader = new R2RHeader(_image, (uint)r2rHeaderDirectory.RelativeVirtualAddress, r2rHeaderOffset);
+                if (r2rHeaderDirectory.Size != R2RHeader.Size)
+                {
+                    throw new System.BadImageFormatException("The calculated size of the R2RHeader doesn't match the size saved in the ManagedNativeHeaderDirectory");
+                }
+            }
+        }
+    }
+}
diff --git a/src/coreclr/src/tools/r2rdump/R2RSection.cs b/src/coreclr/src/tools/r2rdump/R2RSection.cs
new file mode 100644 (file)
index 0000000..b282b2e
--- /dev/null
@@ -0,0 +1,61 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace R2RDump
+{
+    struct R2RSection
+    {
+        public enum SectionType
+        {
+            READYTORUN_SECTION_COMPILER_IDENTIFIER = 100,
+            READYTORUN_SECTION_IMPORT_SECTIONS = 101,
+            READYTORUN_SECTION_RUNTIME_FUNCTIONS = 102,
+            READYTORUN_SECTION_METHODDEF_ENTRYPOINTS = 103,
+            READYTORUN_SECTION_EXCEPTION_INFO = 104,
+            READYTORUN_SECTION_DEBUG_INFO = 105,
+            READYTORUN_SECTION_DELAYLOAD_METHODCALL_THUNKS = 106,
+            READYTORUN_SECTION_AVAILABLE_TYPES = 108,
+            READYTORUN_SECTION_INSTANCE_METHOD_ENTRYPOINTS = 109,
+            READYTORUN_SECTION_INLINING_INFO = 110,
+            READYTORUN_SECTION_PROFILEDATA_INFO = 111
+        }
+
+        /// <summary>
+        /// The ReadyToRun section type
+        /// </summary>
+        public SectionType Type { get; }
+
+        /// <summary>
+        /// The RVA to the section
+        /// </summary>
+        public uint RelativeVirtualAddress { get; }
+
+        /// <summary>
+        /// The size of the section
+        /// </summary>
+        public uint Size { get; }
+
+        public R2RSection(int type, uint rva, uint size)
+        {
+            if (Enum.IsDefined(typeof(SectionType), type)) { 
+                Type = (SectionType)type;
+            }
+            else
+            {
+                throw new System.BadImageFormatException("Invalid ReadyToRun section type");
+            }
+            RelativeVirtualAddress = rva;
+            Size = size;
+        }
+
+        public override string ToString()
+        {
+            StringBuilder sb = new StringBuilder();
+            sb.AppendFormat($"Type:  {Enum.GetName(typeof(SectionType), Type)} ({Type:D})\n");
+            sb.AppendFormat($"RelativeVirtualAddress: 0x{RelativeVirtualAddress:X8}\n");
+            sb.AppendFormat($"Size: {Size} bytes\n");
+            return sb.ToString();
+        }
+    }
+}