+++ /dev/null
-using Microsoft.DiaSymReader.Tools;\r
-using System;\r
-using System.Collections.Generic;\r
-using System.Globalization;\r
-using System.IO;\r
-using System.Text;\r
-using System.Text.RegularExpressions;\r
-using System.Xml;\r
-\r
-namespace Tizen.Runtime.Tools\r
-{\r
- public class LineNumber\r
- {\r
- private static readonly string tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());\r
-\r
- internal sealed class Args\r
- {\r
- public readonly string[] AssemblyPaths;\r
- public readonly string[] PdbPaths;\r
- public readonly string ExceptionPath;\r
- public readonly List<string> ExceptionString;\r
- public readonly string OutputPath;\r
-\r
- public Args(string[] assemblyPaths, string[] pdbPaths, string exceptionPath, List<string> exceptionString, string outputPath)\r
- {\r
- AssemblyPaths = assemblyPaths;\r
- PdbPaths = pdbPaths;\r
- ExceptionPath = exceptionPath;\r
- ExceptionString = exceptionString;\r
- OutputPath = outputPath;\r
- }\r
- }\r
-\r
- public static void UsageLine()\r
- {\r
- string UsageMsg = "Usage: dotnet-extractor convert [Options]\n\n"\r
- + "Interactive shell:\n"\r
- + "<Input string> Input the exception log string directly\n\n"\r
- + "Options:\n"\r
- + " -h, --help Show this help message\n"\r
- + " -i, --input <Input path> Path to the exception log file (File extension: xxxxx.log)\n"\r
- + " -a, --assembly <Path1:Path2:...> Multiple paths with assembly directories separated by colon(':')\n"\r
- + " -p, --pdb <Pdb path> Path to the pdb directory (Can be omitted if it is the same as the assembly directory path)\n"\r
- + " -o, --output <Output path> 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)\n\n"\r
- + "Example:\n"\r
- + "1. If you enter the exception log string directly\n"\r
- + " # dotnet extractor convert\n"\r
- + " Enter the exception log string:\n"\r
- + " I/DOTNET_LAUNCHER(12345): at TestApp.Program.TestMethod() in TestApp.Tizen.dll: token 0x6000001+0x5\n"\r
- + " I/DOTNET_LAUNCHER(12345): at TestApp.Program.OnCreate() in TestApp.Tizen.dll: token 0x6000002+0x1\n\n"\r
- + "2. If both assembly and pdb are in the current directory\n"\r
- + " # dotnet extractor convert --input /tmp/Exception1.log\n\n"\r
- + "3. If both assembly and pdb are in the same directory specified\n"\r
- + " # dotnet extractor convert --input /tmp/Exception2.log --assembly /opt/usr/globalapps/org.tizen.example.TestApp.Tizen/:/usr/share/dotnet.tizen/\n\n"\r
- + "4. If assembly and pdb are separated in each directory\n"\r
- + " # dotnet extractor convert --input /tmp/Exception3.log --assembly /usr/share/dotnet.tizen/framework/ --pdb /tmp/pdbs/\n";\r
- Console.WriteLine(UsageMsg);\r
- }\r
-\r
- public void LineNumbers(string[] args)\r
- {\r
- try\r
- {\r
- Args parsedArgs = ParseArgs(args);\r
- Extract(parsedArgs, Convert(parsedArgs));\r
- }\r
- catch (Exception e)\r
- {\r
- UsageLine();\r
- Console.Error.WriteLine(e.Message);\r
- }\r
- }\r
-\r
- internal static Args ParseArgs(string[] args)\r
- {\r
- string[] assemblyPaths = null;\r
- string[] pdbPaths = null;\r
- string exceptionPath = null;\r
- List<string> exceptionString = new List<string>();\r
- string outputPath = null;\r
-\r
- int i = 1;\r
- while (i < args.Length)\r
- {\r
- var arg = args[i++];\r
- string ReadValue() => (i < args.Length) ? args[i++] : throw new InvalidDataException(string.Format("Missing value for option '{0}'", arg));\r
- switch (arg)\r
- {\r
- case "-h":\r
- case "--help":\r
- UsageLine();\r
- Environment.Exit(0);\r
- break;\r
- case "-i":\r
- case "--input":\r
- exceptionPath = ReadValue();\r
- break;\r
- case "-a":\r
- case "--assembly":\r
- assemblyPaths = ReadValue().Split(":");\r
- break;\r
- case "-p":\r
- case "--pdb":\r
- pdbPaths = ReadValue().Split(":");\r
- break;\r
- case "-o":\r
- case "--output":\r
- outputPath = ReadValue();\r
- break;\r
- default:\r
- UsageLine();\r
- Console.WriteLine($"Unknown option [{arg}]\n");\r
- Environment.Exit(0);\r
- break;\r
- }\r
- }\r
- if (exceptionPath == null)\r
- {\r
- Console.WriteLine("Enter the exception log string:");\r
- string line;\r
- while ((line = Console.ReadLine()) != null && line != "")\r
- {\r
- if (!line.Contains(":"))\r
- {\r
- exceptionPath = line;\r
- exceptionString.Clear();\r
- break;\r
- }\r
- exceptionString.Add(line);\r
- }\r
- }\r
- if (exceptionString.Count == 0)\r
- {\r
- if (exceptionPath == null)\r
- {\r
- throw new InvalidDataException("Missing exception log path or log string\n");\r
- }\r
- else if (!File.Exists(exceptionPath))\r
- {\r
- throw new FileNotFoundException("Exception log file not found\n");\r
- }\r
- }\r
-\r
- assemblyPaths ??= new string[] { Directory.GetCurrentDirectory() };\r
- pdbPaths ??= assemblyPaths;\r
- try\r
- {\r
- outputPath ??= Path.ChangeExtension(exceptionPath, "out");\r
- }\r
- catch (Exception e)\r
- {\r
- throw new InvalidDataException(e.Message);\r
- }\r
-\r
- return new Args(\r
- assemblyPaths: assemblyPaths,\r
- pdbPaths: pdbPaths,\r
- exceptionPath: exceptionPath,\r
- exceptionString: exceptionString,\r
- outputPath: outputPath);\r
- }\r
-\r
- private static List<string> GrabFiles(string[] paths, string searchPattern)\r
- {\r
- List<string> files = new List<string>();\r
- foreach (var assemDir in paths)\r
- {\r
- if (Directory.Exists(assemDir))\r
- {\r
- foreach (var peFile in Directory.GetFiles(assemDir, searchPattern, SearchOption.AllDirectories))\r
- {\r
- files.Add(peFile);\r
- }\r
- }\r
- }\r
- return files;\r
- }\r
-\r
- /*\r
- * Convert\r
- */\r
- private static List<string> Convert(Args args)\r
- {\r
- List<string> peFiles = GrabFiles(args.AssemblyPaths, "*.dll");\r
- if (peFiles.Count == 0)\r
- {\r
- throw new FileNotFoundException("Assembly file not found\n");\r
- }\r
-\r
- List<string> pdbFiles = GrabFiles(args.PdbPaths, "*.pdb");\r
- if (pdbFiles.Count == 0)\r
- {\r
- throw new FileNotFoundException("PDB file not found\n");\r
- }\r
-\r
- Console.Write("Converting pdb to xml...");\r
- Console.SetCursorPosition(0, Console.CursorTop);\r
-\r
- Directory.CreateDirectory(tempDirectory);\r
-\r
- List<string> xmlList = new List<string>();\r
- foreach (var pdbFile in pdbFiles)\r
- {\r
- foreach (var peFile in peFiles)\r
- {\r
- if (Path.GetFileNameWithoutExtension(peFile) == Path.GetFileNameWithoutExtension(pdbFile))\r
- {\r
- string xmlPath = Path.Combine(tempDirectory, Path.GetFileName(Path.ChangeExtension(peFile, "xml")));\r
- GenXmlFromPdb(peFile, pdbFile, xmlPath);\r
- xmlList.Add(xmlPath);\r
- break;\r
- }\r
- }\r
- }\r
- return xmlList;\r
- }\r
-\r
- private static void GenXmlFromPdb(string assemblyPath, string pdbPath, string xmlPath)\r
- {\r
- using var peStream = new FileStream(assemblyPath, FileMode.Open, FileAccess.Read);\r
- using var pdbStream = new FileStream(pdbPath, FileMode.Open, FileAccess.Read);\r
- using var dstFileStream = new FileStream(xmlPath, FileMode.Create, FileAccess.ReadWrite);\r
- using var sw = new StreamWriter(dstFileStream, Encoding.UTF8);\r
- PdbToXmlOptions options = PdbToXmlOptions.ResolveTokens | PdbToXmlOptions.IncludeModuleDebugInfo | PdbToXmlOptions.IncludeTokens;\r
-\r
- PdbToXmlConverter.ToXml(sw, pdbStream, peStream, options);\r
- }\r
-\r
- private static void RemoveTempDirectory()\r
- {\r
- if (Directory.Exists(tempDirectory))\r
- {\r
- Directory.Delete(tempDirectory, true);\r
- }\r
- }\r
-\r
- /*\r
- * Extract\r
- */\r
- private static void Extract(Args args, List<string> xmlList)\r
- {\r
- if (xmlList.Count == 0)\r
- {\r
- RemoveTempDirectory();\r
- throw new FileNotFoundException("Xml file not found\n");\r
- }\r
-\r
- GetLineFromLog(args, xmlList);\r
-\r
- RemoveTempDirectory();\r
- }\r
-\r
- internal sealed class StackTraceInfo\r
- {\r
- public string Type;\r
- public string Method;\r
- public string Param;\r
- public string Assembly;\r
- public string Token;\r
- public string Offset;\r
- public string Document;\r
- public string Filepath;\r
- public string Filename;\r
- public string StartLine;\r
- public string EndLine;\r
- }\r
-\r
- private static string GetRegex(string line, List<string> xmlList)\r
- {\r
- string ret = line;\r
- string logtagStr = Regex.Match(line, "(?<splitdata>.*?) at").Groups["splitdata"].Value;\r
- if (logtagStr.Length != 0)\r
- {\r
- ret = line.Replace(logtagStr, "");\r
- }\r
- string typeMethodStr = Regex.Match(line, "at (?<splitdata>.*?)\\((.*)\\)").Groups["splitdata"].Value;\r
- string methodStr = typeMethodStr.Split(".")[^1];\r
- string typenameStr = typeMethodStr.Replace("." + methodStr, "");\r
- string parameterStr = Regex.Match(line, methodStr + "\\((?<splitdata>.*?)\\)").Groups["splitdata"].Value;\r
- string assemblyStr = Regex.Match(line, " in (?<splitdata>.*?)\\: ").Groups["splitdata"].Value;\r
- string[] tokenOffsetStr = Regex.Match(line, "\\: token (?<splitdata>.*)?").Groups["splitdata"].Value.Split("+");\r
- string xmlStr = assemblyStr.Contains(".ni.dll") ? assemblyStr.Replace(".ni.dll", ".xml") : assemblyStr.Replace(".dll", ".xml");\r
-\r
- if (tokenOffsetStr.Length != 2 || methodStr == "" || typenameStr == "" || assemblyStr == "")\r
- {\r
- return ret;\r
- }\r
-\r
- StackTraceInfo stInfo = new StackTraceInfo() { Type = typenameStr, Method = methodStr, Assembly = assemblyStr, Token = tokenOffsetStr[0], Offset = tokenOffsetStr[1] };\r
-\r
- foreach (var xmlPath in xmlList)\r
- {\r
- if (xmlPath.Contains(xmlStr))\r
- {\r
- GetLineFromXml(xmlPath, stInfo);\r
- if (stInfo.Filepath != null && stInfo.StartLine != null)\r
- {\r
- ret = $" at {stInfo.Type}.{stInfo.Method}({parameterStr}) in {stInfo.Filepath}:line {stInfo.StartLine}";\r
- break;\r
- }\r
- }\r
- }\r
- return ret;\r
- }\r
-\r
- private static void GetLineFromLog(Args args, List<string> xmlList)\r
- {\r
- Console.WriteLine("Extraction result: ");\r
-\r
- try\r
- {\r
- List<string> result = new List<string>();\r
- if (args.ExceptionPath == null)\r
- {\r
- // <Exception String>\r
- foreach (var line in args.ExceptionString)\r
- {\r
- if (!line.Contains("at "))\r
- {\r
- continue;\r
- }\r
- string ret = GetRegex(line, xmlList);\r
- result.Add(ret);\r
- Console.WriteLine(ret);\r
- }\r
- }\r
- else\r
- {\r
- // <Exception Path>\r
- using StreamReader fsr = new StreamReader(new FileStream(args.ExceptionPath, FileMode.Open, FileAccess.Read));\r
- while (!fsr.EndOfStream)\r
- {\r
- string line = fsr.ReadLine();\r
- if (!line.Contains(" at "))\r
- {\r
- continue;\r
- }\r
- string ret = GetRegex(line, xmlList);\r
- result.Add(ret);\r
- Console.WriteLine(ret);\r
- }\r
- }\r
-\r
- string output = string.Empty;\r
- if (args.OutputPath != null)\r
- {\r
- using StreamWriter fsw = new StreamWriter(new FileStream(args.OutputPath, FileMode.Create, FileAccess.Write));\r
- foreach (var ret in result)\r
- {\r
- fsw.WriteLine(ret);\r
- }\r
- output = $"\nOutput: {args.OutputPath}\n";\r
- }\r
- Console.WriteLine($"{output}");\r
- }\r
- catch (Exception e)\r
- {\r
- Console.WriteLine(e);\r
- }\r
- }\r
-\r
- private static void GetLineFromXml(string xmlPath, StackTraceInfo stInfo)\r
- {\r
- try\r
- {\r
- XmlDocument xmlDoc = new XmlDocument();\r
- xmlDoc.Load(xmlPath);\r
- XmlElement xRoot = xmlDoc.DocumentElement;\r
- XmlNodeList xnList = xRoot.ChildNodes;\r
- int xnCount = xnList.Count;\r
- if (xnCount > 0)\r
- {\r
- for (int i = xnCount - 1; i >= 0; i--)\r
- {\r
- XmlNode node = xnList[i];\r
- if (node.Name == "files")\r
- {\r
- ParseFile(node.ChildNodes, stInfo);\r
- }\r
- else if (node.Name == "methods")\r
- {\r
- ParseMethod(node.ChildNodes, stInfo);\r
- }\r
- }\r
- }\r
- }\r
- catch (ArgumentException e)\r
- {\r
- Console.WriteLine(e);\r
- }\r
- }\r
-\r
- private static void ParseFile(XmlNodeList xn, StackTraceInfo stInfo)\r
- {\r
- try\r
- {\r
- foreach (XmlNode node in xn)\r
- {\r
- if (stInfo.Document == node.Attributes["id"].Value)\r
- {\r
- stInfo.Filepath = node.Attributes["name"].Value;\r
- stInfo.Filename = Path.GetFileName(node.Attributes["name"].Value);\r
- }\r
- }\r
- }\r
- catch (ArgumentException e)\r
- {\r
- Console.WriteLine(e);\r
- }\r
- }\r
-\r
- private static void ParseMethod(XmlNodeList xn, StackTraceInfo stInfo)\r
- {\r
- try\r
- {\r
- foreach (XmlNode node in xn)\r
- {\r
- if (stInfo.Type == node.Attributes["containingType"].Value &&\r
- stInfo.Method == node.Attributes["name"].Value &&\r
- stInfo.Token == node.Attributes["token"].Value)\r
- {\r
- if (node.Attributes.Item(2).Name == "parameterNames")\r
- {\r
- stInfo.Param = node.Attributes["parameterNames"].Value;\r
- }\r
- ParseSequence(node.ChildNodes, stInfo);\r
- }\r
- }\r
- }\r
- catch (ArgumentException e)\r
- {\r
- Console.WriteLine(e);\r
- }\r
- }\r
-\r
- private static void ParseSequence(XmlNodeList xn, StackTraceInfo stInfo)\r
- {\r
- try\r
- {\r
- foreach (XmlNode node in xn)\r
- {\r
- if (node.Name == "sequencePoints")\r
- {\r
- ParseEntry(node.ChildNodes, stInfo);\r
- }\r
- }\r
- }\r
- catch (ArgumentException e)\r
- {\r
- Console.WriteLine(e);\r
- }\r
- }\r
-\r
- private static int HexToInt(string value)\r
- {\r
- // strip the leading 0x\r
- if (value.StartsWith("0x", StringComparison.OrdinalIgnoreCase))\r
- {\r
- value = value.Substring(2);\r
- }\r
- return Int32.Parse(value, NumberStyles.HexNumber);\r
- }\r
-\r
- private static void ParseEntry(XmlNodeList xn, StackTraceInfo stInfo)\r
- {\r
- try\r
- {\r
- XmlNode bestPointSoFar = null;\r
- int ilOffset = HexToInt(stInfo.Offset);\r
- foreach (XmlNode node in xn)\r
- {\r
- // If the attribute is not 'startLine', but 'hidden', select the best value so far\r
- if (HexToInt(node.Attributes["offset"].Value) > ilOffset)\r
- {\r
- break;\r
- }\r
- if (node.Attributes["startLine"] != null)\r
- {\r
- bestPointSoFar = node;\r
- }\r
- }\r
- if (bestPointSoFar != null)\r
- {\r
- stInfo.StartLine = bestPointSoFar.Attributes["startLine"].Value;\r
- stInfo.EndLine = bestPointSoFar.Attributes["endLine"].Value;\r
- stInfo.Document = bestPointSoFar.Attributes["document"].Value;\r
- }\r
- }\r
- catch (ArgumentException e)\r
- {\r
- Console.WriteLine(e);\r
- }\r
- }\r
- }\r
-}\r