diff --git a/diagnostics.sln b/diagnostics.sln index dda545db55..a6b54b059e 100644 --- a/diagnostics.sln +++ b/diagnostics.sln @@ -200,6 +200,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Diagnostics.Debug EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExitCodeTracee", "src\tests\ExitCodeTracee\ExitCodeTracee.csproj", "{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-extractor", "src\Tools\dotnet-extractor\dotnet-extractor.csproj", "{14A17FA5-BC1B-499A-B2B3-634ED4479B23}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Checked|Any CPU = Checked|Any CPU @@ -1617,6 +1619,46 @@ Global {61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.RelWithDebInfo|x64.Build.0 = Release|Any CPU {61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU {61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.RelWithDebInfo|x86.Build.0 = Release|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.Checked|Any CPU.ActiveCfg = Debug|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.Checked|Any CPU.Build.0 = Debug|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.Checked|ARM.ActiveCfg = Debug|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.Checked|ARM.Build.0 = Debug|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.Checked|ARM64.ActiveCfg = Debug|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.Checked|ARM64.Build.0 = Debug|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.Checked|x64.ActiveCfg = Debug|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.Checked|x64.Build.0 = Debug|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.Checked|x86.ActiveCfg = Debug|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.Checked|x86.Build.0 = Debug|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.Debug|Any CPU.Build.0 = Debug|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.Debug|ARM.ActiveCfg = Debug|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.Debug|ARM.Build.0 = Debug|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.Debug|ARM64.Build.0 = Debug|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.Debug|x64.ActiveCfg = Debug|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.Debug|x64.Build.0 = Debug|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.Debug|x86.ActiveCfg = Debug|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.Debug|x86.Build.0 = Debug|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.Release|Any CPU.ActiveCfg = Release|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.Release|Any CPU.Build.0 = Release|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.Release|ARM.ActiveCfg = Release|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.Release|ARM.Build.0 = Release|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.Release|ARM64.ActiveCfg = Release|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.Release|ARM64.Build.0 = Release|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.Release|x64.ActiveCfg = Release|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.Release|x64.Build.0 = Release|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.Release|x86.ActiveCfg = Release|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.Release|x86.Build.0 = Release|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.RelWithDebInfo|ARM.ActiveCfg = Release|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.RelWithDebInfo|ARM.Build.0 = Release|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.RelWithDebInfo|ARM64.ActiveCfg = Release|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.RelWithDebInfo|ARM64.Build.0 = Release|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.RelWithDebInfo|x64.Build.0 = Release|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU + {14A17FA5-BC1B-499A-B2B3-634ED4479B23}.RelWithDebInfo|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1668,6 +1710,7 @@ Global {5FC66A16-41E9-4D22-A44C-FEBB7DCCAAF8} = {19FAB78C-3351-4911-8F0C-8C6056401740} {064BC7DD-D44C-400E-9215-7546E092AB98} = {03479E19-3F18-49A6-910A-F5041E27E7C0} {61F73DD0-F346-4D7A-AB12-63415B4EEEE1} = {03479E19-3F18-49A6-910A-F5041E27E7C0} + {14A17FA5-BC1B-499A-B2B3-634ED4479B23} = {B62728C8-1267-4043-B46F-5537BBAEC692} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {46465737-C938-44FC-BE1A-4CE139EBB5E0} diff --git a/documentation/design-docs/dotnet-tools.md b/documentation/design-docs/dotnet-tools.md index b66b5879d5..e0ea250ab3 100644 --- a/documentation/design-docs/dotnet-tools.md +++ b/documentation/design-docs/dotnet-tools.md @@ -784,6 +784,95 @@ Examples: 7,074 4 System.String (Bytes > 1K) [System.Private.CoreLib.dll] ... +### dotnet-extractor + +SYNOPSIS + + dotnet-extractor [options] [command] + +OPTIONS + + --version + Display the version of the dotnet-extractor utility. + + -h, --help + Show command line help + +COMMANDS + + convert Get the line number from the token value in the stacktrace. + +CONVERT + + dotnet-extractor convert [-i|--input ] + [-a|--assembly ] + [-p|--pdb ] + [-o|--output ] + [-h|--help] + + Get the line number from the token value in the stacktrace. + + Usage: + dotnet-extractor convert [options] + + Options: + -i, --input + Path to the exception log file (File extension: xxxxx.log) + + -a, --asembly + Multiple paths with assembly directories separated by colon(':') + + -p, --pdb + Path to the pdb directory (Can be omitted if it is the same as the assembly directory path) + + -o, --output + Path to the output file (Default: Output to console. If omitted, the xxxxx.out file is created in the same location as the log file) + + -h, --help + Show command line help + + Interactive shell: + [input_string] + Input the exception log string directly (--input option is not used) + + Examples: + + 1. If you enter the exception log string directly + $ dotnet-extractor convert + Enter the exception log string: + System.NullReferenceException: Object reference not set to an instance of an object. + at LineNumber.App.CallA() in LineNumber.dll: token 0x6000001+0x5 + at LineNumber.App.CallB() in LineNumber.dll: token 0x6000002+0x1 + + Extraction result: + at LineNumber.App.CallA() in U:\LineNumber\LineNumber\LineNumber_App.cs:line 14 + at LineNumber.App.CallB() in U:\LineNumber\LineNumber\LineNumber_App.cs:line 29 + + 2. If both assembly and pdb are in the current directory + $ dotnet extractor convert --input /tmp/Exception.log + Extraction result: + at LineNumber.App.CallA() in U:\LineNumber\LineNumber\LineNumber_App.cs:line 14 + at LineNumber.App.CallB() in U:\LineNumber\LineNumber\LineNumber_App.cs:line 29 + + Output: /tmp/Exception.out + + 3. If both assembly and pdb are in the same directory specified + $ dotnet extractor convert --input /tmp/Exception.log --assembly /opt/usr/globalapps/ + Extraction result: + at LineNumber.App.CallA() in U:\LineNumber\LineNumber\LineNumber_App.cs:line 14 + at LineNumber.App.CallB() in U:\LineNumber\LineNumber\LineNumber_App.cs:line 29 + + Output: /tmp/Exception.out + + 4. If assembly and pdb are separated in each directory + $ dotnet-extractor convert --input /tmp/Exception.log --assembly /opt/usr/globalapps/:/usr/share/dotnet/ --pdb /opt/share/pdb/ --output /tmp/Result.out + Extraction result: + at LineNumber.App.CallA() in U:\LineNumber\LineNumber\LineNumber_App.cs:line 14 + at LineNumber.App.CallB() in U:\LineNumber\LineNumber\LineNumber_App.cs:line 29 + + Output: /tmp/Result.out + + ## Future suggestions Work described in here captures potential future directions these tools could take given time and customer interest. Some of these might come relatively soon, others feel quite speculative or duplicative with existing technology. Regardless, understanding potential future options helps to ensure that we don't unknowingly paint ourselves into a corner or build an incoherent offering. diff --git a/eng/release/tool-list.json b/eng/release/tool-list.json index 9241225883..26c09014a1 100644 --- a/eng/release/tool-list.json +++ b/eng/release/tool-list.json @@ -25,6 +25,10 @@ { "name": "dotnet-stack", "rids": ["win-x64", "win-x86", "win-arm", "win-arm64", "linux-x64", "linux-musl-arm64", "osx-x64", "linux-arm64", "linux-musl-x64", "linux-arm"] + }, + { + "name": "dotnet-extractor", + "rids": ["win-x64", "win-x86", "win-arm", "win-arm64", "linux-x64", "linux-musl-arm64", "osx-x64", "linux-arm64", "linux-musl-x64", "linux-arm"] } ] }, @@ -36,6 +40,7 @@ "dotnet-sos", "dotnet-trace", "dotnet-stack", + "dotnet-extractor", "Microsoft.Diagnostics.NETCore.Client" ] } diff --git a/src/Tools/dotnet-extractor/ConvertCommand.cs b/src/Tools/dotnet-extractor/ConvertCommand.cs new file mode 100644 index 0000000000..298c374a9c --- /dev/null +++ b/src/Tools/dotnet-extractor/ConvertCommand.cs @@ -0,0 +1,470 @@ +// 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 Microsoft.DiaSymReader.Tools; +using Microsoft.Tools.Common; +using System; +using System.Collections.Generic; +using System.CommandLine; +using System.CommandLine.Binding; +using System.Globalization; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; +using System.Xml; + +namespace Microsoft.Diagnostics.Tools.Extractor +{ + internal static class ConvertCommandHandler + { + // Temporary folder to store the files converted from pdb to xml + private static readonly string tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + + private static string _inputPath; + private static string _outputPath; + private static string[] _assemblyPaths; + private static string[] _pdbPaths; + private static List _inputString; + + delegate void ConvertDelegate(string input, string assembly, string pdb, string output); + + /// + /// .NET Line Number Extractor Tool + /// + /// The input path for file(Exception.log) with exception log + /// All paths in the directory to the assembly where the exception occurred + /// All paths in the directory to the pdb where the exception occurred + /// The output path for the extracted line number data + /// + private static void Convert(string input, string assembly, string pdb, string output) + { + try + { + _inputPath = input; + _outputPath = output; + _inputString = new List(); + if (_inputPath == null) + { + // If no exception file is entered, the exception log message can be entered directly. + Console.Out.WriteLine("Enter the exception log string:"); + string line; + while ((line = Console.ReadLine()) != null && line != "") + { + if (!line.Contains(":")) + { + _inputPath = line; + _inputString.Clear(); + break; + } + _inputString.Add(line); + } + } + if (_inputString.Count == 0) + { + if (_inputPath == null) + { + throw new InvalidDataException("Missing exception log string\n"); + } + else if (!File.Exists(_inputPath)) + { + throw new FileNotFoundException("Exception log file not found\n"); + } + } + + if (assembly != null) + { + _assemblyPaths = assembly.Split(":"); + } + if (pdb != null) + { + _pdbPaths = pdb.Split(":"); + } + if (_inputPath != null) + { + _outputPath ??= Path.ChangeExtension(_inputPath, "out"); + } + + _assemblyPaths ??= new string[] { Directory.GetCurrentDirectory() }; + _pdbPaths ??= _assemblyPaths; + + Extractor(PdbToXmlConvert()); + } + catch (Exception e) + { + Console.Error.WriteLine(e.Message); + } + } + + private static List PdbToXmlConvert() + { + List peFiles = GrabFiles(_assemblyPaths, "*.dll"); + if (peFiles.Count == 0) + { + throw new FileNotFoundException("Assembly file not found\n"); + } + + List pdbFiles = GrabFiles(_pdbPaths, "*.pdb"); + if (pdbFiles.Count == 0) + { + throw new FileNotFoundException("PDB file not found\n"); + } + + Console.Out.Write("Converting pdb to xml..."); + Console.SetCursorPosition(0, Console.CursorTop); + + Directory.CreateDirectory(tempDirectory); + + List xmlList = new List(); + foreach (var pdbFile in pdbFiles) + { + foreach (var peFile in peFiles) + { + if (Path.GetFileNameWithoutExtension(peFile) == Path.GetFileNameWithoutExtension(pdbFile)) + { + string xmlPath = Path.Combine(tempDirectory, Path.GetFileName(Path.ChangeExtension(peFile, "xml"))); + GenXmlFromPdb(peFile, pdbFile, xmlPath); + xmlList.Add(xmlPath); + break; + } + } + } + return xmlList; + } + + private static List GrabFiles(string[] paths, string searchPattern) + { + List files = new List(); + foreach (var assemDir in paths) + { + if (Directory.Exists(assemDir)) + { + foreach (var peFile in Directory.GetFiles(assemDir, searchPattern, SearchOption.AllDirectories)) + { + files.Add(peFile); + } + } + } + return files; + } + + private static void GenXmlFromPdb(string assemblyPath, string pdbPath, string xmlPath) + { + using var peStream = new FileStream(assemblyPath, FileMode.Open, FileAccess.Read); + using var pdbStream = new FileStream(pdbPath, FileMode.Open, FileAccess.Read); + using var dstFileStream = new FileStream(xmlPath, FileMode.Create, FileAccess.ReadWrite); + using var sw = new StreamWriter(dstFileStream, Encoding.UTF8); + PdbToXmlOptions options = PdbToXmlOptions.ResolveTokens | PdbToXmlOptions.IncludeTokens; + + PdbToXmlConverter.ToXml(sw, pdbStream, peStream, options); + } + + private static void RemoveTempDirectory() + { + if (Directory.Exists(tempDirectory)) + { + Directory.Delete(tempDirectory, true); + } + } + + private static void Extractor(List xmlList) + { + if (xmlList.Count == 0) + { + RemoveTempDirectory(); + throw new FileNotFoundException("Xml file not found\n"); + } + + GetLineFromLog(xmlList); + + RemoveTempDirectory(); + } + + internal sealed class StackTraceInfo + { + public string Type; + public string Method; + public string Param; + public string Assembly; + public string Token; + public string Offset; + public string Document; + public string Filepath; + public string Filename; + public string StartLine; + public string EndLine; + } + + private static void GetLineFromLog(List xmlList) + { + Console.Out.WriteLine("Extraction result: "); + + try + { + List outputString = new List(); + if (_inputPath == null) + { + // + foreach (var line in _inputString) + { + if (!line.Contains("at ")) + { + continue; + } + string ret = GetRegex(line, xmlList); + outputString.Add(ret); + Console.Out.WriteLine(ret); + } + } + else + { + // + using StreamReader fsr = new StreamReader(new FileStream(_inputPath, FileMode.Open, FileAccess.Read)); + while (!fsr.EndOfStream) + { + string line = fsr.ReadLine(); + if (!line.Contains("at ")) + { + continue; + } + string ret = GetRegex(line, xmlList); + outputString.Add(ret); + Console.Out.WriteLine(ret); + } + } + + string output = string.Empty; + if (_outputPath != null) + { + using StreamWriter fsw = new StreamWriter(new FileStream(_outputPath, FileMode.Create, FileAccess.Write)); + foreach (var str in outputString) + { + fsw.WriteLine(str); + } + output = $"\nOutput: {_outputPath}\n"; + } + Console.Out.WriteLine($"{output}"); + } + catch (Exception e) + { + Console.Error.WriteLine(e); + } + } + + private static string GetRegex(string line, List xmlList) + { + string ret = line; + string logtagStr = Regex.Match(line, "(?.*?) at").Groups["splitdata"].Value; + if (logtagStr.Length != 0) + { + ret = line.Replace(logtagStr, ""); + } + string typeMethodStr = Regex.Match(line, "at (?.*?)\\((.*)\\)").Groups["splitdata"].Value; + string methodStr = typeMethodStr.Split(".")[typeMethodStr.Split(".").Length - 1]; + string typenameStr = typeMethodStr.Replace("." + methodStr, ""); + string parameterStr = Regex.Match(line, methodStr + "\\((?.*?)\\)").Groups["splitdata"].Value; + string assemblyStr = Regex.Match(line, " in (?.*?)\\: ").Groups["splitdata"].Value; + string[] tokenOffsetStr = Regex.Match(line, "\\: token (?.*)?").Groups["splitdata"].Value.Split("+"); + string xmlStr = assemblyStr.Contains(".ni.dll") ? assemblyStr.Replace(".ni.dll", ".xml") : assemblyStr.Replace(".dll", ".xml"); + + if (tokenOffsetStr.Length != 2 || methodStr == "" || typenameStr == "" || assemblyStr == "") + { + return ret; + } + + StackTraceInfo stInfo = new StackTraceInfo() { Type = typenameStr, Method = methodStr, Assembly = assemblyStr, Token = tokenOffsetStr[0], Offset = tokenOffsetStr[1] }; + + foreach (var xmlPath in xmlList) + { + if (xmlPath.Contains(xmlStr)) + { + GetLineFromXml(xmlPath, stInfo); + if (stInfo.Filepath != null && stInfo.StartLine != null) + { + ret = $" at {stInfo.Type}.{stInfo.Method}({parameterStr}) in {stInfo.Filepath}:line {stInfo.StartLine}"; + break; + } + } + } + return ret; + } + + private static void GetLineFromXml(string xmlPath, StackTraceInfo stInfo) + { + try + { + XmlDocument xmlDoc = new XmlDocument(); + xmlDoc.Load(xmlPath); + XmlElement xRoot = xmlDoc.DocumentElement; + XmlNodeList xnList = xRoot.ChildNodes; + int xnCount = xnList.Count; + if (xnCount > 0) + { + for (int i = xnCount - 1; i >= 0; i--) + { + XmlNode node = xnList[i]; + if (node.Name == "files") + { + ParseFile(node.ChildNodes, stInfo); + } + else if (node.Name == "methods") + { + ParseMethod(node.ChildNodes, stInfo); + } + } + } + } + catch (ArgumentException e) + { + Console.Error.WriteLine(e); + } + } + + private static void ParseFile(XmlNodeList xn, StackTraceInfo stInfo) + { + try + { + foreach (XmlNode node in xn) + { + if (stInfo.Document == node.Attributes["id"].Value) + { + stInfo.Filepath = node.Attributes["name"].Value; + stInfo.Filename = Path.GetFileName(node.Attributes["name"].Value); + } + } + } + catch (ArgumentException e) + { + Console.Error.WriteLine(e); + } + } + + private static void ParseMethod(XmlNodeList xn, StackTraceInfo stInfo) + { + try + { + foreach (XmlNode node in xn) + { + if (stInfo.Type == node.Attributes["containingType"].Value && + stInfo.Method == node.Attributes["name"].Value && + stInfo.Token == node.Attributes["token"].Value) + { + if (node.Attributes.Item(2).Name == "parameterNames") + { + stInfo.Param = node.Attributes["parameterNames"].Value; + } + ParseSequence(node.ChildNodes, stInfo); + } + } + } + catch (ArgumentException e) + { + Console.Error.WriteLine(e); + } + } + + private static void ParseSequence(XmlNodeList xn, StackTraceInfo stInfo) + { + try + { + foreach (XmlNode node in xn) + { + if (node.Name == "sequencePoints") + { + ParseEntry(node.ChildNodes, stInfo); + } + } + } + catch (ArgumentException e) + { + Console.Error.WriteLine(e); + } + } + + private static int HexToInt(string value) + { + // strip the leading 0x + if (value.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) + { + value = value.Substring(2); + } + return Int32.Parse(value, NumberStyles.HexNumber); + } + + private static void ParseEntry(XmlNodeList xn, StackTraceInfo stInfo) + { + try + { + XmlNode bestPointSoFar = null; + int ilOffset = HexToInt(stInfo.Offset); + foreach (XmlNode node in xn) + { + // If the attribute is not 'startLine', but 'hidden', select the best value so far + if (HexToInt(node.Attributes["offset"].Value) > ilOffset) + { + break; + } + if (node.Attributes["startLine"] != null) + { + bestPointSoFar = node; + } + } + if (bestPointSoFar != null) + { + stInfo.StartLine = bestPointSoFar.Attributes["startLine"].Value; + stInfo.EndLine = bestPointSoFar.Attributes["endLine"].Value; + stInfo.Document = bestPointSoFar.Attributes["document"].Value; + } + } + catch (ArgumentException e) + { + Console.Error.WriteLine(e); + } + } + + public static Command ConvertCommand() => + new Command( + name: "convert", description: "Get the line number from the token value in the stacktrace") + { + // Handler + HandlerDescriptor.FromDelegate((ConvertDelegate)Convert).GetCommandHandler(), + // Options + InputOption(), + AssemblyOption(), + PdbOption(), + OutputOption() + }; + + public static Option InputOption() => + new Option( + aliases: new[] { "-i", "--input" }, + description: "Path to the exception log file (File extension: xxxxx.log)") + { + Argument = new Argument(name: "input_path") + }; + + public static Option AssemblyOption() => + new Option( + aliases: new[] { "-a", "--assembly" }, + description: "Multiple paths with assembly directories separated by colon(':')") + { + Argument = new Argument(name: "path1:path2:...") + }; + + public static Option PdbOption() => + new Option( + aliases: new[] { "-p", "--pdb" }, + description: "Path to the pdb directory (Can be omitted if it is the same as the assembly directory path)") + { + Argument = new Argument(name: "path1:path2:...") + }; + + public static Option OutputOption() => + new Option( + aliases: new[] { "-o", "--output" }, + description: "Path to the output file (Default: Output to console. If omitted, the xxxxx.out file is created in the same location as the log file)") + { + Argument = new Argument(name: "output_path") + }; + } +} diff --git a/src/Tools/dotnet-extractor/Program.cs b/src/Tools/dotnet-extractor/Program.cs new file mode 100644 index 0000000000..4e1aaaf8f1 --- /dev/null +++ b/src/Tools/dotnet-extractor/Program.cs @@ -0,0 +1,23 @@ +// 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.CommandLine.Builder; +using System.CommandLine.Parsing; +using System.Threading.Tasks; + +namespace Microsoft.Diagnostics.Tools.Extractor +{ + class Program + { + public static Task Main(string[] args) + { + var parser = new CommandLineBuilder() + .AddCommand(ConvertCommandHandler.ConvertCommand()) + .UseDefaults() + .Build(); + + return parser.InvokeAsync(args); + } + } +} diff --git a/src/Tools/dotnet-extractor/dotnet-extractor.csproj b/src/Tools/dotnet-extractor/dotnet-extractor.csproj new file mode 100644 index 0000000000..69b1cfef07 --- /dev/null +++ b/src/Tools/dotnet-extractor/dotnet-extractor.csproj @@ -0,0 +1,31 @@ + + + netcoreapp3.1 + netcoreapp2.1 + Microsoft.Diagnostics.Tools.Extractor + dotnet-extractor + .NET Line Number Extractor Tool + Diagnostic + $(Description) + $(OutputPath) + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Tools/dotnet-extractor/runtimeconfig.template.json b/src/Tools/dotnet-extractor/runtimeconfig.template.json new file mode 100644 index 0000000000..f022b7ffce --- /dev/null +++ b/src/Tools/dotnet-extractor/runtimeconfig.template.json @@ -0,0 +1,3 @@ +{ + "rollForwardOnNoCandidateFx": 2 +} \ No newline at end of file