From 88c10681e2b9a8584f574df234ee2a2ff74a8ea3 Mon Sep 17 00:00:00 2001 From: Amy Date: Fri, 1 Jun 2018 08:05:44 -0700 Subject: R2RDump - Commandline interface (#18136) * Use ReadCompressedData for NativeHashTable, fix ref signature types, save array dimension, use AppendLine to avoid line-ending problems * Include System.CommandLine, arg parsing, read/write file * Add commandline options to search sections/methods/runtimeFunctions, option to dump raw data * Added comments, save cli options in global variables, renamed some functions * Add DumpByte function to R2RSection and R2RHeader, indent raw bytes * Change some cli option names, use DumpRuntimeFunction, return list of query matches, changes to ArgStringToInt * Move DumpBytes to R2RDump, print method of runtime function * Use writer object instead of Console * Use TextWriter instead of own writer class * Handle jagged arrays and other cases using MethodDefinition.DecodeSignature with a slightly modified DisassemblingTypeProvider * Close the _writer in finally block --- src/tools/r2rdump/DisassemblingTypeProvider.cs | 226 ++++++++++++ src/tools/r2rdump/NativeHashtable.cs | 8 + src/tools/r2rdump/NativeReader.cs | 24 ++ src/tools/r2rdump/R2RDump.cs | 453 +++++++++++++++++++++++-- src/tools/r2rdump/R2RDump.csproj | 4 + src/tools/r2rdump/R2RHeader.cs | 18 +- src/tools/r2rdump/R2RMethod.cs | 147 ++++---- src/tools/r2rdump/R2RReader.cs | 54 +-- src/tools/r2rdump/R2RSection.cs | 6 +- src/tools/r2rdump/SignatureType.cs | 118 ------- 10 files changed, 813 insertions(+), 245 deletions(-) create mode 100644 src/tools/r2rdump/DisassemblingTypeProvider.cs delete mode 100644 src/tools/r2rdump/SignatureType.cs diff --git a/src/tools/r2rdump/DisassemblingTypeProvider.cs b/src/tools/r2rdump/DisassemblingTypeProvider.cs new file mode 100644 index 0000000000..c0a7e148c1 --- /dev/null +++ b/src/tools/r2rdump/DisassemblingTypeProvider.cs @@ -0,0 +1,226 @@ +// 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.Collections.Immutable; +using System.Reflection; +using System.Reflection.Metadata; +using System.Text; + +namespace R2RDump +{ + internal class DisassemblingGenericContext + { + public DisassemblingGenericContext(string[] typeParameters, string[] methodParameters) + { + MethodParameters = methodParameters; + TypeParameters = typeParameters; + } + + public string[] MethodParameters { get; } + public string[] TypeParameters { get; } + } + + // Test implementation of ISignatureTypeProvider that uses strings in ilasm syntax as TType. + // A real provider in any sort of perf constraints would not want to allocate strings freely like this, but it keeps test code simple. + internal class DisassemblingTypeProvider : ISignatureTypeProvider + { + public virtual string GetPrimitiveType(PrimitiveTypeCode typeCode) + { + return typeCode.ToString(); + } + + public virtual string GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind = 0) + { + TypeDefinition definition = reader.GetTypeDefinition(handle); + + string name = definition.Namespace.IsNil + ? reader.GetString(definition.Name) + : reader.GetString(definition.Namespace) + "." + reader.GetString(definition.Name); + + if ((definition.Attributes & TypeAttributes.NestedPublic) != 0 || (definition.Attributes & TypeAttributes.NestedFamily) != 0) + { + TypeDefinitionHandle declaringTypeHandle = definition.GetDeclaringType(); + return GetTypeFromDefinition(reader, declaringTypeHandle, 0) + "/" + name; + } + + return name; + } + + public virtual string GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind = 0) + { + TypeReference reference = reader.GetTypeReference(handle); + Handle scope = reference.ResolutionScope; + + string name = reference.Namespace.IsNil + ? reader.GetString(reference.Name) + : reader.GetString(reference.Namespace) + "." + reader.GetString(reference.Name); + + switch (scope.Kind) + { + case HandleKind.ModuleReference: + return "[.module " + reader.GetString(reader.GetModuleReference((ModuleReferenceHandle)scope).Name) + "]" + name; + + case HandleKind.AssemblyReference: + var assemblyReferenceHandle = (AssemblyReferenceHandle)scope; + var assemblyReference = reader.GetAssemblyReference(assemblyReferenceHandle); + return "[" + reader.GetString(assemblyReference.Name) + "]" + name; + + case HandleKind.TypeReference: + return GetTypeFromReference(reader, (TypeReferenceHandle)scope) + "/" + name; + + default: + return name; + } + } + + public virtual string GetTypeFromSpecification(MetadataReader reader, DisassemblingGenericContext genericContext, TypeSpecificationHandle handle, byte rawTypeKind = 0) + { + return reader.GetTypeSpecification(handle).DecodeSignature(this, genericContext); + } + + public virtual string GetSZArrayType(string elementType) + { + return elementType + "[]"; + } + + public virtual string GetPointerType(string elementType) + { + return elementType + "*"; + } + + public virtual string GetByReferenceType(string elementType) + { + return "ref " + elementType; + } + + public virtual string GetGenericMethodParameter(DisassemblingGenericContext genericContext, int index) + { + if (index >= genericContext.MethodParameters.Length) + { + R2RDump.WriteWarning("GenericMethodParameters index out of bounds"); + return ""; + } + return genericContext.MethodParameters[index]; + } + + public virtual string GetGenericTypeParameter(DisassemblingGenericContext genericContext, int index) + { + if (index >= genericContext.TypeParameters.Length) + { + R2RDump.WriteWarning("GenericTypeParameter index out of bounds"); + return ""; + } + return genericContext.TypeParameters[index]; + } + + public virtual string GetPinnedType(string elementType) + { + return elementType + " pinned"; + } + + public virtual string GetGenericInstantiation(string genericType, ImmutableArray typeArguments) + { + return genericType + "<" + String.Join(",", typeArguments) + ">"; + } + + public virtual string GetArrayType(string elementType, ArrayShape shape) + { + var builder = new StringBuilder(); + + builder.Append(elementType); + builder.Append('['); + + for (int i = 0; i < shape.Rank; i++) + { + int lowerBound = 0; + + if (i < shape.LowerBounds.Length) + { + lowerBound = shape.LowerBounds[i]; + builder.Append(lowerBound); + } + + builder.Append("..."); + + if (i < shape.Sizes.Length) + { + builder.Append(lowerBound + shape.Sizes[i] - 1); + } + + if (i < shape.Rank - 1) + { + builder.Append(','); + } + } + + builder.Append(']'); + + return builder.ToString(); + } + + public virtual string GetTypeFromHandle(MetadataReader reader, DisassemblingGenericContext genericContext, EntityHandle handle) + { + switch (handle.Kind) + { + case HandleKind.TypeDefinition: + return GetTypeFromDefinition(reader, (TypeDefinitionHandle)handle); + + case HandleKind.TypeReference: + return GetTypeFromReference(reader, (TypeReferenceHandle)handle); + + case HandleKind.TypeSpecification: + return GetTypeFromSpecification(reader, genericContext, (TypeSpecificationHandle)handle); + + default: + throw new ArgumentOutOfRangeException(nameof(handle)); + } + } + + public virtual string GetModifiedType(string modifierType, string unmodifiedType, bool isRequired) + { + return unmodifiedType + (isRequired ? " modreq(" : " modopt(") + modifierType + ")"; + } + + public virtual string GetFunctionPointerType(MethodSignature signature) + { + ImmutableArray parameterTypes = signature.ParameterTypes; + + int requiredParameterCount = signature.RequiredParameterCount; + + var builder = new StringBuilder(); + builder.Append("method "); + builder.Append(signature.ReturnType); + builder.Append(" *("); + + int i; + for (i = 0; i < requiredParameterCount; i++) + { + builder.Append(parameterTypes[i]); + if (i < parameterTypes.Length - 1) + { + builder.Append(", "); + } + } + + if (i < parameterTypes.Length) + { + builder.Append("..., "); + for (; i < parameterTypes.Length; i++) + { + builder.Append(parameterTypes[i]); + if (i < parameterTypes.Length - 1) + { + builder.Append(", "); + } + } + } + + builder.Append(')'); + return builder.ToString(); + } + } + +} diff --git a/src/tools/r2rdump/NativeHashtable.cs b/src/tools/r2rdump/NativeHashtable.cs index 902080017b..b596622b1d 100644 --- a/src/tools/r2rdump/NativeHashtable.cs +++ b/src/tools/r2rdump/NativeHashtable.cs @@ -47,6 +47,14 @@ namespace R2RDump return val; } + public uint GetCompressedData() + { + int off = (int)Offset; + uint val = NativeReader.ReadCompressedData(_image, ref off); + Offset += 1; + return val; + } + public uint GetUnsigned() { uint value = 0; diff --git a/src/tools/r2rdump/NativeReader.cs b/src/tools/r2rdump/NativeReader.cs index 13b674433f..b3359f7632 100644 --- a/src/tools/r2rdump/NativeReader.cs +++ b/src/tools/r2rdump/NativeReader.cs @@ -200,5 +200,29 @@ namespace R2RDump return offset; } + + public static uint ReadCompressedData(byte[] image, ref int start) + { + int off = start; + uint data = ReadUInt32(image, ref off); + if ((data & 0x80) == 0x00) + { + start++; + return (byte)data; + } + if ((data & 0xC0) == 0x80) // 10?? ???? + { + data = (uint)((ReadByte(image, ref start) & 0x3f) << 8); + data |= ReadByte(image, ref start); + } + else // 110? ???? + { + data = (uint)(ReadByte(image, ref start) & 0x1f) << 24; + data |= (uint)ReadByte(image, ref start) << 16; + data |= (uint)ReadByte(image, ref start) << 8; + data |= ReadByte(image, ref start); + } + return data; + } } } diff --git a/src/tools/r2rdump/R2RDump.cs b/src/tools/r2rdump/R2RDump.cs index 7d0bd2fd19..9aa80395f5 100644 --- a/src/tools/r2rdump/R2RDump.cs +++ b/src/tools/r2rdump/R2RDump.cs @@ -4,55 +4,470 @@ using System; using System.Collections.Generic; +using System.CommandLine; +using System.IO; namespace R2RDump { class R2RDump { - public static void OutputWarning(string warning) + private bool _help = false; + private IReadOnlyList _inputFilenames = Array.Empty(); + private string _outputFilename = null; + private bool _raw = false; + private bool _header = false; + private bool _disasm = false; + private IReadOnlyList _queries = Array.Empty(); + private IReadOnlyList _keywords = Array.Empty(); + private IReadOnlyList _runtimeFunctions = Array.Empty(); + private IReadOnlyList _sections = Array.Empty(); + private bool _diff = false; + private TextWriter _writer; + + private R2RDump() + { + } + + private ArgumentSyntax ParseCommandLine(string[] args) + { + ArgumentSyntax argSyntax = ArgumentSyntax.Parse(args, syntax => + { + syntax.ApplicationName = "R2RDump"; + syntax.HandleHelp = false; + syntax.HandleErrors = true; + + syntax.DefineOption("h|help", ref _help, "Help message for R2RDump"); + syntax.DefineOptionList("i|in", ref _inputFilenames, "Input file(s) to dump. Expects them to by ReadyToRun images"); + syntax.DefineOption("o|out", ref _outputFilename, "Output file path. Dumps everything to the specified file except help message and exception messages"); + syntax.DefineOption("v|verbose|raw", ref _raw, "Dump the raw bytes of each section or runtime function"); + syntax.DefineOption("header", ref _header, "Dump R2R header"); + syntax.DefineOption("d|disasm", ref _disasm, "Show disassembly of methods or runtime functions"); + syntax.DefineOptionList("q|query", ref _queries, "Query method by exact name, signature, row id or token"); + syntax.DefineOptionList("k|keyword", ref _keywords, "Search method by keyword"); + syntax.DefineOptionList("r|runtimefunction", ref _runtimeFunctions, ArgStringToInt, "Get one runtime function by id or relative virtual address"); + syntax.DefineOptionList("s|section", ref _sections, "Get section by keyword"); + syntax.DefineOption("diff", ref _diff, "Compare two R2R images"); // not yet implemented + }); + + return argSyntax; + } + + private int ArgStringToInt(string arg) + { + int n; + if (!ArgStringToInt(arg, out n)) + { + throw new ArgumentException("Can't parse argument to int"); + } + return n; + } + + /// + /// Converts string passed as cmd line args into int, works for hexidecimal with 0x as prefix + /// + /// The argument string to convert + /// The integer representation + private bool ArgStringToInt(string arg, out int n) + { + arg = arg.Trim(); + if (arg.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) + { + return int.TryParse(arg.Substring(2), System.Globalization.NumberStyles.HexNumber, null, out n); + } + return int.TryParse(arg, out n); + } + + public static void WriteWarning(string warning) { Console.WriteLine($"Warning: {warning}"); } - public static int Main(string[] args) + private void WriteDivider(string title) { - try + _writer.WriteLine("============== " + title + " =============="); + _writer.WriteLine(); + } + + private void WriteSubDivider() + { + _writer.WriteLine("------------------"); + _writer.WriteLine(); + } + + /// + /// Dumps the R2RHeader and all the sections in the header + /// + private void DumpHeader(R2RReader r2r, bool dumpSections) + { + _writer.WriteLine(r2r.R2RHeader.ToString()); + if (_raw) + { + DumpBytes(r2r, r2r.R2RHeader.RelativeVirtualAddress, (uint)r2r.R2RHeader.Size); + } + if (dumpSections) + { + WriteDivider("R2R Sections"); + foreach (R2RSection section in r2r.R2RHeader.Sections.Values) + { + DumpSection(r2r, section); + } + } + } + + /// + /// Dumps one R2RSection + /// + private void DumpSection(R2RReader r2r, R2RSection section) + { + WriteSubDivider(); + _writer.WriteLine(section.ToString()); + if (_raw) + { + DumpBytes(r2r, section.RelativeVirtualAddress, (uint)section.Size); + } + } + + /// + /// Dumps one R2RMethod. + /// + private void DumpMethod(R2RReader r2r, R2RMethod method) + { + WriteSubDivider(); + _writer.WriteLine(method.ToString()); + + foreach (RuntimeFunction runtimeFunction in method.RuntimeFunctions) + { + DumpRuntimeFunction(r2r, runtimeFunction); + } + } + + /// + /// Dumps one runtime function. + /// + private void DumpRuntimeFunction(R2RReader r2r, RuntimeFunction rtf) + { + _writer.WriteLine(rtf.ToString()); + if (_raw) + { + DumpBytes(r2r, rtf.StartAddress, (uint)rtf.Size); + } + _writer.WriteLine(); + } + + /// + /// Prints a formatted string containing a block of bytes from the relative virtual address and size + /// + public void DumpBytes(R2RReader r2r, int rva, uint size) + { + uint start = (uint)r2r.GetOffset(rva); + if (start > r2r.Image.Length || start + size > r2r.Image.Length) + { + throw new IndexOutOfRangeException(); + } + _writer.Write(" "); + if (rva % 16 != 0) + { + int floor = rva / 16 * 16; + _writer.Write($"{floor:X8}:"); + _writer.Write(new String(' ', (rva - floor) * 3)); + } + for (uint i = 0; i < size; i++) + { + if ((rva + i) % 16 == 0) + { + _writer.Write($"{rva + i:X8}:"); + } + _writer.Write($" {r2r.Image[start + i]:X2}"); + if ((rva + i) % 16 == 15 && i != size - 1) + { + _writer.WriteLine(); + _writer.Write(" "); + } + } + _writer.WriteLine(); + } + + // + /// For each query in the list of queries, search for all methods matching the query by name, signature or id + /// + /// Contains all the extracted info about the ReadyToRun image + /// The title to print, "R2R Methods by Query" or "R2R Methods by Keyword" + /// The keywords/ids to search for + /// Specifies whether to look for methods with names/signatures/ids matching the method exactly or partially + private void QueryMethod(R2RReader r2r, string title, IReadOnlyList queries, bool exact) + { + if (queries.Count > 0) { - if (args.Length < 1) + WriteDivider(title); + } + foreach (string q in queries) + { + IList res = FindMethod(r2r, q, exact); + + _writer.WriteLine(res.Count + " result(s) for \"" + q + "\""); + _writer.WriteLine(); + foreach (R2RMethod method in res) { - throw new System.ArgumentException("File name must be passed as argument"); + DumpMethod(r2r, method); } + } + } - R2RReader r2r = new R2RReader(args[0]); + // + /// For each query in the list of queries, search for all sections by the name or value of the ReadyToRunSectionType enum + /// + /// Contains all the extracted info about the ReadyToRun image + /// The names/values to search for + private void QuerySection(R2RReader r2r, IReadOnlyList queries) + { + if (queries.Count > 0) + { + WriteDivider("R2R Section"); + } + foreach (string q in queries) + { + IList res = FindSection(r2r, q); - if (r2r.IsR2R) + _writer.WriteLine(res.Count + " result(s) for \"" + q + "\""); + _writer.WriteLine(); + foreach (R2RSection section in res) { - Console.WriteLine($"Filename: {r2r.Filename}"); - Console.WriteLine($"Machine: {r2r.Machine}"); - Console.WriteLine($"ImageBase: 0x{r2r.ImageBase:X8}"); + DumpSection(r2r, section); + } + } + } + + // + /// For each query in the list of queries, search for a runtime function by id. + /// The method containing the runtime function gets outputted, along with the single runtime function that was searched + /// + /// Contains all the extracted info about the ReadyToRun image + /// The ids to search for + private void QueryRuntimeFunction(R2RReader r2r, IReadOnlyList queries) + { + if (queries.Count > 0) + { + WriteDivider("Runtime Functions"); + } + foreach (int q in queries) + { + RuntimeFunction rtf = FindRuntimeFunction(r2r, q); - Console.WriteLine("============== R2R Header =============="); - Console.WriteLine(r2r.R2RHeader.ToString()); - foreach (KeyValuePair section in r2r.R2RHeader.Sections) + if (rtf == null) + { + WriteWarning("Unable to find by id " + q); + continue; + } + _writer.WriteLine(rtf.Method.SignatureString); + DumpRuntimeFunction(r2r, rtf); + } + } + + /// + /// Outputs specified headers, sections, methods or runtime functions for one ReadyToRun image + /// + /// The structure containing the info of the ReadyToRun image + public void Dump(R2RReader r2r) + { + _writer.WriteLine($"Filename: {r2r.Filename}"); + _writer.WriteLine($"Machine: {r2r.Machine}"); + _writer.WriteLine($"ImageBase: 0x{r2r.ImageBase:X8}"); + _writer.WriteLine(); + + if (_queries.Count == 0 && _keywords.Count == 0 && _runtimeFunctions.Count == 0 && _sections.Count == 0) //dump all sections and methods + { + WriteDivider("R2R Header"); + DumpHeader(r2r, true); + + if (!_header) + { + WriteDivider("R2R Methods"); + _writer.WriteLine(); + foreach (R2RMethod method in r2r.R2RMethods) { - Console.WriteLine("------------------\n"); - Console.WriteLine(section.Value.ToString()); + DumpMethod(r2r, method); } + } + } + else //dump queried sections/methods/runtimeFunctions + { + if (_header) + { + DumpHeader(r2r, false); + } - Console.WriteLine("============== Native Code ==============\n"); - foreach (R2RMethod method in r2r.R2RMethods) + QuerySection(r2r, _sections); + QueryRuntimeFunction(r2r, _runtimeFunctions); + QueryMethod(r2r, "R2R Methods by Query", _queries, true); + QueryMethod(r2r, "R2R Methods by Keyword", _keywords, false); + } + + _writer.WriteLine("========================================================"); + _writer.WriteLine(); + } + + /// + /// Returns true if the name/signature/id of method matches query + /// + /// Specifies exact or partial match + /// Case-insensitive and ignores whitespace + private bool Match(R2RMethod method, string query, bool exact) + { + int id; + bool isNum = ArgStringToInt(query, out id); + bool idMatch = isNum && (method.Rid == id || method.Token == id); + + bool sigMatch = false; + if (exact) + { + sigMatch = method.Name.Equals(query, StringComparison.OrdinalIgnoreCase); + if (!sigMatch) + { + string sig = method.SignatureString.Replace(" ", ""); + string q = query.Replace(" ", ""); + int iMatch = sig.IndexOf(q, StringComparison.OrdinalIgnoreCase); + sigMatch = (iMatch == 0 || (iMatch > 0 && iMatch == (sig.Length - q.Length) && sig[iMatch - 1] == '.')); + } + } + else + { + string sig = method.Signature.ReturnType + method.SignatureString.Replace(" ", ""); + sigMatch = (sig.IndexOf(query.Replace(" ", ""), StringComparison.OrdinalIgnoreCase) >= 0); + } + + return idMatch || sigMatch; + } + + /// + /// Returns true if the name or value of the ReadyToRunSectionType of section matches query + /// + /// Case-insensitive + private bool Match(R2RSection section, string query) + { + int queryInt; + bool isNum = ArgStringToInt(query, out queryInt); + string typeName = Enum.GetName(typeof(R2RSection.SectionType), section.Type); + + return (isNum && (int)section.Type == queryInt) || typeName.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0; + } + + /// + /// Finds all R2RMethods by name/signature/id matching query + /// + /// Contains all extracted info about the ReadyToRun image + /// The name/signature/id to search for + /// Specifies exact or partial match + /// List of all matching methods + /// Case-insensitive and ignores whitespace + public IList FindMethod(R2RReader r2r, string query, bool exact) + { + List res = new List(); + foreach (R2RMethod method in r2r.R2RMethods) + { + if (Match(method, query, exact)) + { + res.Add(method); + } + } + return res; + } + + /// + /// Finds all R2RSections by name or value of the ReadyToRunSectionType matching query + /// + /// Contains all extracted info about the ReadyToRun image + /// The name or value to search for + /// List of all matching sections + /// Case-insensitive + public IList FindSection(R2RReader r2r, string query) + { + List res = new List(); + foreach (R2RSection section in r2r.R2RHeader.Sections.Values) + { + if (Match(section, query)) + { + res.Add(section); + } + } + return res; + } + + /// + /// Returns the runtime function with id matching rtfQuery + /// + /// Contains all extracted info about the ReadyToRun image + /// The name or value to search for + public RuntimeFunction FindRuntimeFunction(R2RReader r2r, int rtfQuery) + { + foreach (R2RMethod m in r2r.R2RMethods) + { + foreach (RuntimeFunction rtf in m.RuntimeFunctions) + { + if (rtf.Id == rtfQuery || (rtf.StartAddress >= rtfQuery && rtf.StartAddress + rtf.Size < rtfQuery)) { - Console.Write(method.ToString()); - Console.WriteLine("------------------\n"); + return rtf; } } } + return null; + } + + private int Run(string[] args) + { + ArgumentSyntax syntax = ParseCommandLine(args); + + if (_help) + { + _writer.WriteLine(syntax.GetHelpText()); + return 0; + } + + if (_inputFilenames.Count == 0) + throw new ArgumentException("Input filename must be specified (--in )"); + + // open output stream + if (_outputFilename != null) + { + _writer = File.CreateText(_outputFilename); + } + else + { + _writer = Console.Out; + } + + try + { + foreach (string filename in _inputFilenames) + { + R2RReader r2r = new R2RReader(filename); + Dump(r2r); + } + } catch (Exception e) { Console.WriteLine("Error: " + e.ToString()); return 1; } + finally + { + // close output stream + _writer.Close(); + } + return 0; } + + public static int Main(string[] args) + { + try + { + return new R2RDump().Run(args); + } + catch (Exception e) + { + Console.WriteLine("Error: " + e.ToString()); + return 1; + } + } } } diff --git a/src/tools/r2rdump/R2RDump.csproj b/src/tools/r2rdump/R2RDump.csproj index b9b5b5cd6c..cb9e76fb4d 100644 --- a/src/tools/r2rdump/R2RDump.csproj +++ b/src/tools/r2rdump/R2RDump.csproj @@ -9,4 +9,8 @@ true + + + + diff --git a/src/tools/r2rdump/R2RHeader.cs b/src/tools/r2rdump/R2RHeader.cs index 33ef42431a..96eacd583a 100644 --- a/src/tools/r2rdump/R2RHeader.cs +++ b/src/tools/r2rdump/R2RHeader.cs @@ -74,7 +74,7 @@ namespace R2RDump Signature = NativeReader.ReadUInt32(image, ref curOffset); if (Signature != READYTORUN_SIGNATURE) { - throw new System.BadImageFormatException("Incorrect R2R header signature"); + throw new System.BadImageFormatException("Incorrect R2R header signature: " + SignatureString); } MajorVersion = NativeReader.ReadUInt16(image, ref curOffset); @@ -89,7 +89,7 @@ namespace R2RDump var sectionType = (R2RSection.SectionType)type; if (!Enum.IsDefined(typeof(R2RSection.SectionType), type)) { - R2RDump.OutputWarning("Invalid ReadyToRun section type"); + R2RDump.WriteWarning("Invalid ReadyToRun section type"); } Sections[sectionType] = new R2RSection(sectionType, NativeReader.ReadInt32(image, ref curOffset), @@ -102,19 +102,19 @@ namespace R2RDump public override string ToString() { StringBuilder sb = new StringBuilder(); - sb.AppendFormat($"Signature: 0x{Signature:X8} ({SignatureString})\n"); - sb.AppendFormat($"RelativeVirtualAddress: 0x{RelativeVirtualAddress:X8}\n"); + sb.AppendLine($"Signature: 0x{Signature:X8} ({SignatureString})"); + sb.AppendLine($"RelativeVirtualAddress: 0x{RelativeVirtualAddress:X8}"); 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"); + sb.AppendLine($"Size: {Size} bytes"); + sb.AppendLine($"MajorVersion: 0x{MajorVersion:X4}"); + sb.AppendLine($"MinorVersion: 0x{MinorVersion:X4}"); + sb.AppendLine($"Flags: 0x{Flags:X8}"); foreach (ReadyToRunFlag flag in Enum.GetValues(typeof(ReadyToRunFlag))) { if ((Flags & (uint)flag) != 0) { - sb.AppendFormat($" - {Enum.GetName(typeof(ReadyToRunFlag), flag)}\n"); + sb.AppendLine($" - {Enum.GetName(typeof(ReadyToRunFlag), flag)}"); } } } diff --git a/src/tools/r2rdump/R2RMethod.cs b/src/tools/r2rdump/R2RMethod.cs index fd077b6b7c..1b658df40a 100644 --- a/src/tools/r2rdump/R2RMethod.cs +++ b/src/tools/r2rdump/R2RMethod.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; @@ -11,7 +12,7 @@ using System.Text; namespace R2RDump { - struct RuntimeFunction + class RuntimeFunction { /// /// The index of the runtime function @@ -37,7 +38,12 @@ namespace R2RDump /// public int UnwindRVA { get; } - public RuntimeFunction(int id, int startRva, int endRva, int unwindRva) + /// + /// The method that this runtime function belongs to + /// + public R2RMethod Method { get; } + + public RuntimeFunction(int id, int startRva, int endRva, int unwindRva, R2RMethod method) { Id = id; StartAddress = startRva; @@ -45,21 +51,22 @@ namespace R2RDump if (endRva == -1) Size = -1; UnwindRVA = unwindRva; + Method = method; } public override string ToString() { StringBuilder sb = new StringBuilder(); - sb.AppendFormat($"Id: {Id}\n"); - sb.AppendFormat($"StartAddress: 0x{StartAddress:X8}\n"); + sb.AppendLine($"Id: {Id}"); + sb.AppendLine($"StartAddress: 0x{StartAddress:X8}"); if (Size == -1) { - sb.Append("Size: Unavailable\n"); + sb.AppendLine("Size: Unavailable"); } else { - sb.AppendFormat($"Size: {Size} bytes\n"); + sb.AppendLine($"Size: {Size} bytes"); } return sb.ToString(); @@ -78,17 +85,24 @@ namespace R2RDump /// public string Name { get; } + /// + /// The signature with format: namespace.class.methodName(S, T, ...) + /// + public string SignatureString { get; } + public bool IsGeneric { get; } - /// + /*/// /// The return type of the method /// - public SignatureType ReturnType { get; } + public string ReturnType { get; } /// /// The argument types of the method /// - public SignatureType[] ArgTypes { get; } + public string[] ArgTypes { get; }*/ + + public MethodSignature Signature { get; } /// /// The type that the method belongs to @@ -100,10 +114,15 @@ namespace R2RDump /// public uint Token { get; } + /// + /// The row id of the method + /// + public uint Rid { get; } + /// /// All the runtime functions of this method /// - public List RuntimeFunctions { get; } + public IList RuntimeFunctions { get; } /// /// The id of the entrypoint runtime function @@ -113,7 +132,7 @@ namespace R2RDump /// /// Maps all the generic parameters to the type in the instance /// - Dictionary _genericParamInstanceMap; + Dictionary _genericParamInstanceMap; [Flags] public enum EncodeMethodSigFlags @@ -156,6 +175,7 @@ namespace R2RDump public R2RMethod(byte[] image, MetadataReader mdReader, uint rid, int entryPointId, GenericElementTypes[] instanceArgs, uint[] tok) { Token = _mdtMethodDef | rid; + Rid = rid; EntryPointRuntimeFunctionId = entryPointId; _mdReader = mdReader; @@ -187,7 +207,7 @@ namespace R2RDump SignatureHeader signatureHeader = signatureReader.ReadSignatureHeader(); IsGeneric = signatureHeader.IsGeneric; GenericParameterHandleCollection genericParams = _methodDef.GetGenericParameters(); - _genericParamInstanceMap = new Dictionary(); + _genericParamInstanceMap = new Dictionary(); int argCount = signatureReader.ReadCompressedInteger(); if (IsGeneric) @@ -195,17 +215,16 @@ namespace R2RDump argCount = signatureReader.ReadCompressedInteger(); } - ReturnType = new SignatureType(ref signatureReader, mdReader, genericParams); - ArgTypes = new SignatureType[argCount]; - for (int i = 0; i < argCount; i++) - { - ArgTypes[i] = new SignatureType(ref signatureReader, mdReader, genericParams); - } - + DisassemblingTypeProvider provider = new DisassemblingTypeProvider(); if (IsGeneric && instanceArgs != null && tok != null) { InitGenericInstances(genericParams, instanceArgs, tok); } + + DisassemblingGenericContext genericContext = new DisassemblingGenericContext(new string[0], _genericParamInstanceMap.Values.ToArray()); + Signature = _methodDef.DecodeSignature(provider, genericContext); + + SignatureString = GetSignature(); } private void InitGenericInstances(GenericParameterHandleCollection genericParams, GenericElementTypes[] instanceArgs, uint[] tok) @@ -215,80 +234,66 @@ namespace R2RDump throw new BadImageFormatException("Generic param indices out of bounds"); } - for (int i = 0; i < genericParams.Count; i++) + for (int i = 0; i < instanceArgs.Length; i++) { - var key = _mdReader.GetString(_mdReader.GetGenericParameter(genericParams.ElementAt(i)).Name); - + string key = _mdReader.GetString(_mdReader.GetGenericParameter(genericParams.ElementAt(i)).Name); + string name = instanceArgs[i].ToString(); if (instanceArgs[i] == GenericElementTypes.ValueType) { - string classname = _mdReader.GetString(_mdReader.GetTypeDefinition(MetadataTokens.TypeDefinitionHandle((int)tok[i])).Name); - _genericParamInstanceMap[key] = new GenericInstance(instanceArgs[i], classname); - } - else - { - _genericParamInstanceMap[key] = new GenericInstance(instanceArgs[i], Enum.GetName(typeof(GenericElementTypes), instanceArgs[i])); - } - } - - if ((ReturnType.Flags & SignatureType.SignatureTypeFlags.GENERIC) != 0) - { - ReturnType.GenericInstance = _genericParamInstanceMap[ReturnType.TypeName]; - } + var t = _mdReader.GetTypeDefinition(MetadataTokens.TypeDefinitionHandle((int)tok[i])); + name = _mdReader.GetString(t.Name); - for (int i = 0; i < ArgTypes.Length; i++) - { - if ((ArgTypes[i].Flags & SignatureType.SignatureTypeFlags.GENERIC) != 0) - { - ArgTypes[i].GenericInstance = _genericParamInstanceMap[ArgTypes[i].TypeName]; } + _genericParamInstanceMap[key] = name; } } - public override string ToString() + private string GetSignature() { StringBuilder sb = new StringBuilder(); - if (Name != null) - { - sb.AppendFormat($"{ReturnType.ToString()} "); - sb.AppendFormat($"{DeclaringType}{Name}"); - - if (IsGeneric) - { - sb.Append("<"); - int i = 0; - foreach (var instance in _genericParamInstanceMap.Values) - { - if (i > 0) - { - sb.Append(", "); - } - sb.AppendFormat($"{instance.TypeName}"); - i++; - } - sb.Append(">"); - } + sb.AppendFormat($"{DeclaringType}{Name}"); - sb.Append("("); - for (int i = 0; i < ArgTypes.Length; i++) + if (IsGeneric) + { + sb.Append("<"); + int i = 0; + foreach (var instance in _genericParamInstanceMap.Values) { if (i > 0) { sb.Append(", "); } - sb.AppendFormat($"{ArgTypes[i].ToString()}"); + sb.AppendFormat($"{instance}"); + i++; } - sb.Append(")\n"); + sb.Append(">"); } - sb.AppendFormat($"Token: 0x{Token:X8}\n"); - sb.AppendFormat($"EntryPointRuntimeFunctionId: {EntryPointRuntimeFunctionId}\n"); - sb.AppendFormat($"Number of RuntimeFunctions: {RuntimeFunctions.Count}\n\n"); - - foreach (RuntimeFunction runtimeFunction in RuntimeFunctions) + sb.Append("("); + for (int i = 0; i < Signature.ParameterTypes.Length; i++) { - sb.AppendFormat($"{runtimeFunction}\n"); + if (i > 0) + { + sb.Append(", "); + } + sb.AppendFormat($"{Signature.ParameterTypes[i]}"); } + sb.Append(")"); + + return sb.ToString(); + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + + sb.AppendLine($"{Signature.ReturnType} {SignatureString}"); + + sb.AppendLine($"Token: 0x{Token:X8}"); + sb.AppendLine($"Rid: {Rid}"); + sb.AppendLine($"EntryPointRuntimeFunctionId: {EntryPointRuntimeFunctionId}"); + sb.AppendLine($"Number of RuntimeFunctions: {RuntimeFunctions.Count}"); return sb.ToString(); } diff --git a/src/tools/r2rdump/R2RReader.cs b/src/tools/r2rdump/R2RReader.cs index 343cf27344..18969d1bb6 100644 --- a/src/tools/r2rdump/R2RReader.cs +++ b/src/tools/r2rdump/R2RReader.cs @@ -7,17 +7,18 @@ using System.Collections.Generic; using System.IO; using System.Reflection.Metadata; using System.Reflection.PortableExecutable; +using System.Text; namespace R2RDump { class R2RReader { + private readonly PEReader peReader; + /// /// Byte array containing the ReadyToRun image /// - private readonly byte[] _image; - - private readonly PEReader peReader; + public byte[] Image { get; } /// /// Name of the image file @@ -49,22 +50,22 @@ namespace R2RDump /// The runtime functions and method signatures of each method /// TODO: generic methods /// - public List R2RMethods { get; } + public IList R2RMethods { get; } /// - /// Initializes the fields of the R2RHeader + /// Initializes the fields of the R2RHeader and R2RMethods /// /// PE image /// The Cor header flag must be ILLibrary public unsafe R2RReader(string filename) { Filename = filename; - _image = File.ReadAllBytes(filename); + Image = File.ReadAllBytes(filename); - fixed (byte* p = _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); if (!IsR2R) @@ -78,7 +79,7 @@ namespace R2RDump // initialize R2RHeader DirectoryEntry r2rHeaderDirectory = peReader.PEHeaders.CorHeader.ManagedNativeHeaderDirectory; int r2rHeaderOffset = GetOffset(r2rHeaderDirectory.RelativeVirtualAddress); - R2RHeader = new R2RHeader(_image, r2rHeaderDirectory.RelativeVirtualAddress, r2rHeaderOffset); + 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"); @@ -107,15 +108,15 @@ namespace R2RDump // initialize R2RMethods with method signatures from MethodDefHandle, and runtime function indices from MethodDefEntryPoints int methodDefEntryPointsRVA = R2RHeader.Sections[R2RSection.SectionType.READYTORUN_SECTION_METHODDEF_ENTRYPOINTS].RelativeVirtualAddress; int methodDefEntryPointsOffset = GetOffset(methodDefEntryPointsRVA); - NativeArray methodEntryPoints = new NativeArray(_image, (uint)methodDefEntryPointsOffset); + NativeArray methodEntryPoints = new NativeArray(Image, (uint)methodDefEntryPointsOffset); uint nMethodEntryPoints = methodEntryPoints.GetCount(); R2RMethods = new List(); for (uint rid = 1; rid <= nMethodEntryPoints; rid++) { int offset = 0; - if (methodEntryPoints.TryGetAt(_image, rid - 1, ref offset)) + if (methodEntryPoints.TryGetAt(Image, rid - 1, ref offset)) { - R2RMethod method = new R2RMethod(_image, mdReader, rid, GetEntryPointIdFromOffset(offset), null, null); + R2RMethod method = new R2RMethod(Image, mdReader, rid, GetEntryPointIdFromOffset(offset), null, null); if (method.EntryPointRuntimeFunctionId < 0 || method.EntryPointRuntimeFunctionId >= nRuntimeFunctions) { @@ -129,17 +130,17 @@ namespace R2RDump // instance method table R2RSection instMethodEntryPointSection = R2RHeader.Sections[R2RSection.SectionType.READYTORUN_SECTION_INSTANCE_METHOD_ENTRYPOINTS]; int instMethodEntryPointsOffset = GetOffset(instMethodEntryPointSection.RelativeVirtualAddress); - NativeParser parser = new NativeParser(_image, (uint)instMethodEntryPointsOffset); - NativeHashtable instMethodEntryPoints = new NativeHashtable(_image, parser); + NativeParser parser = new NativeParser(Image, (uint)instMethodEntryPointsOffset); + NativeHashtable instMethodEntryPoints = new NativeHashtable(Image, parser); NativeHashtable.AllEntriesEnumerator allEntriesEnum = instMethodEntryPoints.EnumerateAllEntries(); NativeParser curParser = allEntriesEnum.GetNext(); while (!curParser.IsNull()) { - byte methodFlags = curParser.GetByte(); - byte rid = curParser.GetByte(); + uint methodFlags = curParser.GetCompressedData(); + uint rid = curParser.GetCompressedData(); if ((methodFlags & (byte)R2RMethod.EncodeMethodSigFlags.ENCODE_METHOD_SIG_MethodInstantiation) != 0) { - byte nArgs = curParser.GetByte(); + uint nArgs = curParser.GetCompressedData(); R2RMethod.GenericElementTypes[] args = new R2RMethod.GenericElementTypes[nArgs]; uint[] tokens = new uint[nArgs]; for (int i = 0; i < nArgs; i++) @@ -147,14 +148,14 @@ namespace R2RDump args[i] = (R2RMethod.GenericElementTypes)curParser.GetByte(); if (args[i] == R2RMethod.GenericElementTypes.ValueType) { - tokens[i] = curParser.GetByte(); + tokens[i] = curParser.GetCompressedData(); tokens[i] = (tokens[i] >> 2); } } uint id = curParser.GetUnsigned(); id = id >> 1; - R2RMethod method = new R2RMethod(_image, mdReader, rid, (int)id, args, tokens); + R2RMethod method = new R2RMethod(Image, mdReader, rid, (int)id, args, tokens); if (method.EntryPointRuntimeFunctionId >= 0 && method.EntryPointRuntimeFunctionId < nRuntimeFunctions) { isEntryPoint[method.EntryPointRuntimeFunctionId] = true; @@ -174,15 +175,15 @@ namespace R2RDump curOffset = runtimeFunctionOffset + runtimeFunctionId * runtimeFunctionSize; do { - int startRva = NativeReader.ReadInt32(_image, ref curOffset); + int startRva = NativeReader.ReadInt32(Image, ref curOffset); int endRva = -1; if (Machine == Machine.Amd64) { - endRva = NativeReader.ReadInt32(_image, ref curOffset); + endRva = NativeReader.ReadInt32(Image, ref curOffset); } - int unwindRva = NativeReader.ReadInt32(_image, ref curOffset); + int unwindRva = NativeReader.ReadInt32(Image, ref curOffset); - method.RuntimeFunctions.Add(new RuntimeFunction(runtimeFunctionId, startRva, endRva, unwindRva)); + method.RuntimeFunctions.Add(new RuntimeFunction(runtimeFunctionId, startRva, endRva, unwindRva, method)); runtimeFunctionId++; } while (runtimeFunctionId < nRuntimeFunctions && !isEntryPoint[runtimeFunctionId]); @@ -202,17 +203,20 @@ namespace R2RDump return rva - containingSection.VirtualAddress + containingSection.PointerToRawData; } + /// + /// Reads the method entrypoint from the offset. Used for non-generic methods + /// private int GetEntryPointIdFromOffset(int offset) { // get the id of the entry point runtime function from the MethodEntryPoints NativeArray uint id = 0; // the RUNTIME_FUNCTIONS index - offset = (int)NativeReader.DecodeUnsigned(_image, (uint)offset, ref id); + offset = (int)NativeReader.DecodeUnsigned(Image, (uint)offset, ref id); if ((id & 1) != 0) { if ((id & 2) != 0) { uint val = 0; - NativeReader.DecodeUnsigned(_image, (uint)offset, ref val); + NativeReader.DecodeUnsigned(Image, (uint)offset, ref val); offset -= (int)val; } // TODO: Dump fixups diff --git a/src/tools/r2rdump/R2RSection.cs b/src/tools/r2rdump/R2RSection.cs index 0d44b7aa0d..aef32d24ba 100644 --- a/src/tools/r2rdump/R2RSection.cs +++ b/src/tools/r2rdump/R2RSection.cs @@ -46,9 +46,9 @@ namespace R2RDump 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"); + sb.AppendLine($"Type: {Enum.GetName(typeof(SectionType), Type)} ({Type:D})"); + sb.AppendLine($"RelativeVirtualAddress: 0x{RelativeVirtualAddress:X8}"); + sb.AppendLine($"Size: {Size} bytes"); return sb.ToString(); } } diff --git a/src/tools/r2rdump/SignatureType.cs b/src/tools/r2rdump/SignatureType.cs deleted file mode 100644 index e5efcfb115..0000000000 --- a/src/tools/r2rdump/SignatureType.cs +++ /dev/null @@ -1,118 +0,0 @@ -// 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.Reflection.Metadata; -using System.Text; - -namespace R2RDump -{ - class SignatureType - { - /// - /// Indicates if the type is an array, reference, or generic - /// - public SignatureTypeFlags Flags { get; } - - /// - /// Name of the object or primitive type, the placeholder type for generic methods - /// - public string TypeName { get; } - - /// - /// The type that the generic method was instantiated to - /// - public GenericInstance GenericInstance { get; set; } - - [Flags] - public enum SignatureTypeFlags - { - NONE = 0x00, - ARRAY = 0x01, - REFERENCE = 0x02, - GENERIC = 0x04, - }; - - public SignatureType(ref BlobReader signatureReader, MetadataReader mdReader, GenericParameterHandleCollection genericParams) - { - SignatureTypeCode signatureTypeCode = signatureReader.ReadSignatureTypeCode(); - Flags = 0; - if (signatureTypeCode == SignatureTypeCode.SZArray) - { - Flags |= SignatureTypeFlags.ARRAY; - signatureTypeCode = signatureReader.ReadSignatureTypeCode(); - } - - TypeName = signatureTypeCode.ToString(); - if (signatureTypeCode == SignatureTypeCode.TypeHandle || signatureTypeCode == SignatureTypeCode.ByReference) - { - if (signatureTypeCode == SignatureTypeCode.ByReference) - { - Flags |= SignatureTypeFlags.REFERENCE; - } - - EntityHandle handle = signatureReader.ReadTypeHandle(); - if (handle.Kind == HandleKind.TypeDefinition) - { - TypeDefinition typeDef = mdReader.GetTypeDefinition((TypeDefinitionHandle)handle); - TypeName = mdReader.GetString(typeDef.Name); - } - else if (handle.Kind == HandleKind.TypeReference) - { - TypeReference typeRef = mdReader.GetTypeReference((TypeReferenceHandle)handle); - TypeName = mdReader.GetString(typeRef.Name); - } - } - else if (signatureTypeCode == SignatureTypeCode.GenericMethodParameter) - { - int index = signatureReader.ReadCompressedInteger(); - GenericParameter generic = mdReader.GetGenericParameter(genericParams[index]); - TypeName = mdReader.GetString(generic.Name); - Flags |= SignatureTypeFlags.GENERIC; - } - } - - public override string ToString() - { - StringBuilder sb = new StringBuilder(); - if ((Flags & SignatureTypeFlags.REFERENCE) != 0) - { - sb.Append("ref "); - } - - if ((Flags & SignatureTypeFlags.GENERIC) != 0) - { - sb.AppendFormat($"{GenericInstance.TypeName}"); - } - else - { - sb.AppendFormat($"{TypeName}"); - } - if ((Flags & SignatureTypeFlags.ARRAY) != 0) - { - sb.Append("[]"); - } - return sb.ToString(); - } - } - - struct GenericInstance - { - /// - /// The type of the instance for generic a type - /// - public R2RMethod.GenericElementTypes Instance { get; } - - /// - /// The type name of the instance for generic a type. Different from GenericInstance.Instance for structs (ValueType) - /// - public string TypeName { get; } - - public GenericInstance(R2RMethod.GenericElementTypes instance, string name) - { - Instance = instance; - TypeName = name; - } - } -} -- cgit v1.2.3