Switch R2RDump over to the new command line parser (dotnet/coreclr#27440)
authorTomáš Rylek <trylek@microsoft.com>
Fri, 25 Oct 2019 22:45:19 +0000 (15:45 -0700)
committerGitHub <noreply@github.com>
Fri, 25 Oct 2019 22:45:19 +0000 (15:45 -0700)
This change modifies R2RDump to use the new
System.CommandLine.Experimental module instead of the obsoleted
System.CommandLine parser we were using before. Apart from general
hygiene w.r.t. using up-to-date components one practical reason
for this switch-over is that the old parser is only available on
the unreliable myget.org nuget feed we plan to drop as part of
our planned repo consolidation.

During debugging of the change I noticed a bug in the implementation
of the "--verbose" option - it shouldn't be setting EntryPoints,
otherwise we actually end up with a super-terse output including
just the entry points. I have anecdotally verified that, after fixing
this in both the "new" and "old" R2RDump both produce identical output.

Thanks

Tomas

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

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

diff --git a/src/coreclr/src/tools/r2rdump/CommandLineOptions.cs b/src/coreclr/src/tools/r2rdump/CommandLineOptions.cs
new file mode 100644 (file)
index 0000000..e5a27af
--- /dev/null
@@ -0,0 +1,44 @@
+// 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.CommandLine;
+using System.CommandLine.Builder;
+using System.CommandLine.Invocation;
+using System.IO;
+
+namespace R2RDump
+{
+    internal static class CommandLineOptions
+    {
+        public static RootCommand RootCommand()
+        {
+            RootCommand command = new RootCommand();
+            command.AddOption(new Option(new[] { "--in", "-i" }, "Input file(s) to dump. Expects them to by ReadyToRun images", new Argument<FileInfo[]>()));
+            command.AddOption(new Option(new[] { "--out", "-o" }, "Output file path. Dumps everything to the specified file except for help message and exception messages", new Argument<FileInfo>()));
+            command.AddOption(new Option(new[] { "--xml", "-x" }, "Output in XML format", new Argument<bool>()));
+            command.AddOption(new Option(new[] { "--raw" }, "Dump the raw bytes of each section or runtime function", new Argument<bool>()));
+            command.AddOption(new Option(new[] { "--header" }, "Dump R2R header", new Argument<bool>()));
+            command.AddOption(new Option(new[] { "--disasm", "-d" }, "Show disassembly of methods or runtime functions", new Argument<bool>()));
+            command.AddOption(new Option(new[] { "--naked" }, "Naked dump suppresses most compilation details like placement addresses", new Argument<bool>()));
+            command.AddOption(new Option(new[] { "--query", "-q" }, "Query method by exact name, signature, row ID or token", new Argument<string[]>()));
+            command.AddOption(new Option(new[] { "--keyword", "-k" }, "Search method by keyword", new Argument<string[]>()));
+            command.AddOption(new Option(new[] { "--runtimefunction", "-f" }, "Get one runtime function by id or relative virtual address", new Argument<string[]>()));
+            command.AddOption(new Option(new[] { "--section", "-s" }, "Get section by keyword", new Argument<string[]>()));
+            command.AddOption(new Option(new[] { "--unwind" }, "Dump unwindInfo", new Argument<bool>()));
+            command.AddOption(new Option(new[] { "--gc" }, "Dump gcInfo and slot table", new Argument<bool>()));
+            command.AddOption(new Option(new[] { "--sectionContents", "--sc" }, "Dump section contents", new Argument<bool>()));
+            command.AddOption(new Option(new[] { "--entrypoints", "-e" }, "Dump list of method / instance entrypoints in the R2R file", new Argument<bool>()));
+            command.AddOption(new Option(new[] { "--normalize", "-n" }, "Normalize dump by sorting the various tables and methods (default = unsorted i.e. file order)", new Argument<bool>()));
+            command.AddOption(new Option(new[] { "--verbose", "-v" }, "Dump disassembly, unwindInfo, gcInfo and sectionContents", new Argument<bool>()));
+            command.AddOption(new Option(new[] { "--diff" }, "Compare two R2R images", new Argument<bool>()));
+            command.AddOption(new Option(new[] { "--ignoreSensitive" }, "Ignore sensitive properties in xml dump to avoid failing tests", new Argument<bool>()));
+            command.AddOption(new Option(new[] { "--reference", "-r" }, "Explicit reference assembly files", new Argument<FileInfo[]>()));
+            command.AddOption(new Option(new[] { "--referencePath", "--rp" }, "Search paths for reference assemblies", new Argument<DirectoryInfo[]>()));
+            command.AddOption(new Option(new[] { "--inlineSignatureBinary", "--isb" }, "Embed binary signature into its textual representation", new Argument<bool>()));
+            command.AddOption(new Option(new[] { "--signatureBinary", "--sb" }, "Append signature binary to its textual representation", new Argument<bool>()));
+            return command;
+        }
+    }
+}
index 3a97d5d..f8127ec 100644 (file)
@@ -5,32 +5,47 @@
 using System;
 using System.Collections.Generic;
 using System.CommandLine;
+using System.CommandLine.Invocation;
 using System.IO;
 using System.Linq;
-using System.Reflection.Metadata;
 using System.Reflection.Metadata.Ecma335;
 using System.Text;
+using System.Threading.Tasks;
 using System.Xml;
-using System.Xml.Serialization;
 
 namespace R2RDump
 {
     public class DumpOptions
     {
-        public bool Raw;
-        public bool Normalize;
-        public bool Naked;
-        public bool Header;
-        public bool Disasm;
-        public bool Unwind;
-        public bool GC;
-        public bool SectionContents;
-        public bool EntryPoints;
-        public bool SignatureBinary;
-        public bool InlineSignatureBinary;
-
-        public IReadOnlyList<string> ReferenceAssemblies = Array.Empty<string>();
-        public IReadOnlyList<string> ReferencePaths = Array.Empty<string>();
+        public FileInfo[] In { get; set; }
+        public FileInfo Out { get; set; }
+
+        public bool Xml { get; set; }
+        public bool Raw { get; set; }
+        public bool Header { get; set; }
+        public bool Disasm { get; set; }
+        public bool Naked { get; set; }
+
+        public string[] Query { get; set; }
+        public string[] Keyword { get; set; }
+        public string[] RuntimeFunction { get; set; }
+        public string[] Section { get; set; }
+
+        public bool Unwind { get; set; }
+        public bool GC { get; set; }
+        public bool SectionContents { get; set; }
+        public bool EntryPoints { get; set; }
+        public bool Normalize { get; set; }
+        public bool Verbose { get; set; }
+        public bool Diff { get; set; }
+        public bool IgnoreSensitive { get; set; }
+
+        public FileInfo[] Reference { get; set; }
+        public DirectoryInfo[] ReferencePath { get; set; }
+
+        public bool SignatureBinary { get; set; }
+        public bool InlineSignatureBinary { get; set; }
+
         public Dictionary<string, EcmaMetadataReader> AssemblyCache = new Dictionary<string, EcmaMetadataReader>(StringComparer.OrdinalIgnoreCase);
 
         /// <summary>
@@ -47,15 +62,16 @@ namespace R2RDump
         /// <returns></returns>
         public string FindAssembly(string simpleName, string parentFile)
         {
-            foreach (string refAsm in ReferenceAssemblies)
+            foreach (FileInfo refAsm in Reference ?? Enumerable.Empty<FileInfo>())
             {
-                if (Path.GetFileNameWithoutExtension(refAsm).Equals(simpleName, StringComparison.OrdinalIgnoreCase))
+                if (Path.GetFileNameWithoutExtension(refAsm.FullName).Equals(simpleName, StringComparison.OrdinalIgnoreCase))
                 {
-                    return refAsm;
+                    return refAsm.FullName;
                 }
             }
 
-            IEnumerable<string> allRefPaths = new string[] { Path.GetDirectoryName(parentFile) }.Concat(ReferencePaths);
+            IEnumerable<string> allRefPaths = new string[] { Path.GetDirectoryName(parentFile) }
+                .Concat((ReferencePath ?? Enumerable.Empty<DirectoryInfo>()).Select(path => path.FullName));
 
             foreach (string refPath in allRefPaths)
             {
@@ -134,77 +150,35 @@ namespace R2RDump
 
     class R2RDump
     {
-        // Options set by user specifying what to dump
-        private bool _help;
-        private IReadOnlyList<string> _inputFilenames = Array.Empty<string>();
-        private string _outputFilename = null;
-        private DumpOptions _options = new DumpOptions();
-        private IReadOnlyList<string> _queries = Array.Empty<string>();
-        private IReadOnlyList<string> _keywords = Array.Empty<string>();
-        private IReadOnlyList<int> _runtimeFunctions = Array.Empty<int>();
-        private IReadOnlyList<string> _sections = Array.Empty<string>();
-        private bool _diff;
-        private bool _xml;
-        private TextWriter _writer;
-        private Dictionary<R2RSection.SectionType, bool> _selectedSections = new Dictionary<R2RSection.SectionType, bool>();
+        private readonly DumpOptions _options;
+        private readonly TextWriter _writer;
+        private readonly Dictionary<R2RSection.SectionType, bool> _selectedSections = new Dictionary<R2RSection.SectionType, bool>();
         private Dumper _dumper;
-        private bool _ignoreSensitive;
 
-        private R2RDump()
+        private R2RDump(DumpOptions options)
         {
-        }
+            _options = options;
 
-        /// <summary>
-        /// Parse commandline options
-        /// </summary>
-        private ArgumentSyntax ParseCommandLine(string[] args)
-        {
-            bool verbose = false;
-            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("x|xml", ref _xml, "Output in XML format");
-                syntax.DefineOption("raw", ref _options.Raw, "Dump the raw bytes of each section or runtime function");
-                syntax.DefineOption("header", ref _options.Header, "Dump R2R header");
-                syntax.DefineOption("d|disasm", ref _options.Disasm, "Show disassembly of methods or runtime functions");
-                syntax.DefineOption("naked", ref _options.Naked, "Naked dump suppresses most compilation details like placement addresses");
-                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("f|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("unwind", ref _options.Unwind, "Dump unwindInfo");
-                syntax.DefineOption("gc", ref _options.GC, "Dump gcInfo and slot table");
-                syntax.DefineOption("sc", ref _options.SectionContents, "Dump section contents");
-                syntax.DefineOption("e|entrypoints", ref _options.EntryPoints, "Dump list of method / instance entrypoints in the R2R file");
-                syntax.DefineOption("n|normalize", ref _options.Normalize, "Normalize dump by sorting the various tables and methods (default = unsorted i.e. file order)");
-                syntax.DefineOption("v|verbose", ref verbose, "Dump disassembly, unwindInfo, gcInfo and section contents");
-                syntax.DefineOption("diff", ref _diff, "Compare two R2R images");
-                syntax.DefineOption("ignoreSensitive", ref _ignoreSensitive, "Ignores sensitive properties in xml dump to avoid failing tests");
-                syntax.DefineOptionList("r|reference", ref _options.ReferenceAssemblies, "Explicit reference assembly files");
-                syntax.DefineOptionList("rp|referencepath", ref _options.ReferencePaths, "Search paths for reference assemblies");
-                syntax.DefineOption("isb|inlineSignatureBinary", ref _options.InlineSignatureBinary, "Embed binary signature into its textual description");
-                syntax.DefineOption("sb|signatureBinary", ref _options.SignatureBinary, "Append signature binary to its textual description");
-            });
-
-            if (verbose)
+            if (_options.Verbose)
             {
                 _options.Disasm = true;
                 _options.Unwind = true;
                 _options.GC = true;
                 _options.SectionContents = true;
-                _options.EntryPoints = true;
             }
 
-            return argSyntax;
+            // open output stream
+            if (_options.Out != null)
+            {
+                _writer = new StreamWriter(_options.Out.FullName, append: false, encoding: Encoding.ASCII);
+            }
+            else
+            {
+                _writer = Console.Out;
+            }
         }
 
-        private int ArgStringToInt(string arg)
+        private static int ArgStringToInt(string arg)
         {
             int n;
             if (!ArgStringToInt(arg, out n))
@@ -215,13 +189,13 @@ namespace R2RDump
         }
 
         /// <summary>
-        /// Converts string passed as cmd line args into int, works for hexidecimal with 0x as prefix
+        /// Converts string passed as cmd line args into int, works for hexadecimal with 0x as prefix
         /// </summary>
         /// <param name="arg">The argument string to convert</param>
         /// <param name="n">The integer representation</param>
-        private bool ArgStringToInt(string arg, out int n)
+        private static bool ArgStringToInt(string arg, out int n)
         {
-                       arg = arg.Trim();
+            arg = arg.Trim();
             if (arg.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
             {
                 return int.TryParse(arg.Substring(2), System.Globalization.NumberStyles.HexNumber, null, out n);
@@ -290,15 +264,15 @@ namespace R2RDump
         /// </summary>
         /// <param name="r2r">Contains all the extracted info about the ReadyToRun image</param>
         /// <param name="queries">The ids to search for</param>
-        private void QueryRuntimeFunction(R2RReader r2r, IReadOnlyList<int> queries)
+        private void QueryRuntimeFunction(R2RReader r2r, IEnumerable<string> queries)
         {
-            if (queries.Count > 0)
+            if (queries.Any())
             {
                 _dumper.WriteDivider("Runtime Functions");
             }
-            foreach (int q in queries)
+            foreach (string q in queries)
             {
-                RuntimeFunction rtf = FindRuntimeFunction(r2r, q);
+                RuntimeFunction rtf = FindRuntimeFunction(r2r, ArgStringToInt(q));
 
                 if (rtf == null)
                 {
@@ -318,14 +292,40 @@ namespace R2RDump
         {
             _dumper.Begin();
 
-            if (_queries.Count == 0 && _keywords.Count == 0 && _runtimeFunctions.Count == 0 && _sections.Count == 0) //dump all sections and methods if no queries specified
+            if (_options.Header || !_options.EntryPoints)
             {
-                if (_options.Header || !_options.EntryPoints)
-                {
-                    _dumper.WriteDivider("R2R Header");
-                    _dumper.DumpHeader(true);
-                }
-                
+                _dumper.WriteDivider("R2R Header");
+                _dumper.DumpHeader(true);
+            }
+
+            bool haveQuery = false;
+            if (_options.Section != null)
+            {
+                haveQuery = true;
+                QuerySection(r2r, _options.Section);
+            }
+
+            if (_options.RuntimeFunction != null)
+            {
+                haveQuery = true;
+                QueryRuntimeFunction(r2r, _options.RuntimeFunction);
+            }
+
+            if (_options.Query != null)
+            {
+                haveQuery = true;
+                QueryMethod(r2r, "R2R Methods by Query", _options.Query, true);
+            }
+
+            if (_options.Keyword != null)
+            {
+                haveQuery = true;
+                QueryMethod(r2r, "R2R Methods by Keyword", _options.Keyword, false);
+            }
+
+            if (!haveQuery)
+            {
+                // Dump all sections and methods if no queries specified
                 if (_options.EntryPoints)
                 {
                     _dumper.DumpEntryPoints();
@@ -336,18 +336,6 @@ namespace R2RDump
                     _dumper.DumpAllMethods();
                 }
             }
-            else //dump queried sections, methods and runtimeFunctions
-            {
-                if (_options.Header)
-                {
-                    _dumper.DumpHeader(false);
-                }
-
-                QuerySection(r2r, _sections);
-                QueryRuntimeFunction(r2r, _runtimeFunctions);
-                QueryMethod(r2r, "R2R Methods by Query", _queries, true);
-                QueryMethod(r2r, "R2R Methods by Keyword", _keywords, false);
-            }
 
             _dumper.End();
         }
@@ -458,34 +446,16 @@ namespace R2RDump
             return null;
         }
 
-        private int Run(string[] args)
+        private int Run()
         {
-            ArgumentSyntax syntax = ParseCommandLine(args);
-
-            // open output stream
-            if (_outputFilename != null)
-            {
-                _writer = new StreamWriter(_outputFilename, append: false, encoding: Encoding.ASCII);
-            }
-            else
-            {
-                _writer = Console.Out;
-            }
-
-            if (_help)
-            {
-                _writer.WriteLine(syntax.GetHelpText());
-                return 0;
-            }
-
             Disassembler disassembler = null;
 
             try
             {
-                if (_inputFilenames.Count == 0)
+                if (_options.In == null || _options.In.Length == 0)
                     throw new ArgumentException("Input filename must be specified (--in <file>)");
 
-                if (_diff && _inputFilenames.Count < 2)
+                if (_options.Diff && _options.In.Length < 2)
                     throw new ArgumentException("Need at least 2 input files in diff mode");
 
                 if (_options.Naked && _options.Raw)
@@ -495,10 +465,10 @@ namespace R2RDump
 
                 R2RReader previousReader = null;
 
-                foreach (string filename in _inputFilenames)
+                foreach (FileInfo filename in _options.In)
                 {
                     // parse the ReadyToRun image
-                    R2RReader r2r = new R2RReader(_options, filename);
+                    R2RReader r2r = new R2RReader(_options, filename.FullName);
 
                     if (_options.Disasm)
                     {
@@ -512,16 +482,16 @@ namespace R2RDump
                         }
                     }
 
-                    if (_xml)
+                    if (_options.Xml)
                     {
-                        _dumper = new XmlDumper(_ignoreSensitive, r2r, _writer, disassembler, _options);
+                        _dumper = new XmlDumper(_options.IgnoreSensitive, r2r, _writer, disassembler, _options);
                     }
                     else
                     {
                         _dumper = new TextDumper(r2r, _writer, disassembler, _options);
                     }
 
-                    if (!_diff)
+                    if (!_options.Diff)
                     {
                         // output the ReadyToRun info
                         Dump(r2r);
@@ -540,9 +510,8 @@ namespace R2RDump
                 if (e is ArgumentException)
                 {
                     Console.WriteLine();
-                    Console.WriteLine(syntax.GetHelpText());
                 }
-                if (_xml)
+                if (_options.Xml)
                 {
                     XmlDocument document = new XmlDocument();
                     XmlNode node = document.CreateNode("element", "Error", "");
@@ -568,17 +537,11 @@ namespace R2RDump
             return 0;
         }
 
-        public static int Main(string[] args)
+        public static async Task<int> Main(string[] args)
         {
-            try
-            {
-                return new R2RDump().Run(args);
-            }
-            catch (Exception e)
-            {
-                Console.WriteLine("Error: " + e.ToString());
-                return 1;
-            }
+            var command = CommandLineOptions.RootCommand();
+            command.Handler = CommandHandler.Create<DumpOptions>((DumpOptions options) => new R2RDump(options).Run());
+            return await command.InvokeAsync(args);
         }
     }
 }
index e2ab3e8..f165c42 100644 (file)
@@ -7,9 +7,7 @@
     <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
     <AssemblyKey>Open</AssemblyKey>
     <IsDotNetFrameworkProductAssembly>true</IsDotNetFrameworkProductAssembly>
-    <TargetFramework>netcoreapp2.0</TargetFramework>
-    <RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
-    <TargetFrameworkIdentifier>.NETCoreApp</TargetFrameworkIdentifier>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
     <CLSCompliant>false</CLSCompliant>
     <NoWarn>8002,NU1701</NoWarn>
     <RuntimeIdentifiers>win-x64;win-x86</RuntimeIdentifiers>
@@ -20,8 +18,8 @@
     <PackageReference Include="Microsoft.NETCore.CoreDisTools">
       <Version>1.0.1-prerelease-00005</Version>
     </PackageReference>
-    <PackageReference Include="System.CommandLine">
-      <Version>0.1.0-e160119-1</Version>
+    <PackageReference Include="System.CommandLine.Experimental">
+      <Version>0.2.0-alpha.19174.3</Version>
     </PackageReference>
     <PackageReference Include="System.Reflection.Metadata">
       <Version>1.6.0</Version>