--- /dev/null
+using Microsoft.DiaSymReader.Tools;\r
+using System;\r
+using System.Collections.Generic;\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
+ public LineNumber()\r
+ {\r
+ }\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 string OutputPath;\r
+\r
+ public Args(string[] assemblyPaths, string[] pdbPaths, string exceptionPath, string outputPath)\r
+ {\r
+ AssemblyPaths = assemblyPaths;\r
+ PdbPaths = pdbPaths;\r
+ ExceptionPath = exceptionPath;\r
+ OutputPath = outputPath;\r
+ }\r
+ }\r
+\r
+ public static void UsageLine()\r
+ {\r
+ string UsageMsg = "Usage: dotnet-extractor line-number [Log] [Options]\n\n"\r
+ + "Log:\n"\r
+ + "<Filepath> Path to the exception log file (Filename extension: xxxxx.log)\n\n"\r
+ //+ "<String> Input the log message directly\n\n"\r
+ + "Options:\n"\r
+ + " -h, --help Show this help message\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, --out <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 both assembly and pdb are in the current directory\n"\r
+ + " # dotnet extractor line-number /tmp/Exception1.log\n"\r
+ + "2. If both assembly and pdb are in the same directory specified\n"\r
+ + " # dotnet extractor line-number /tmp/Exception2.log --assembly /opt/usr/globalapps/org.tizen.example.TestApp/:/usr/share/dotnet.tizen/\n"\r
+ + "3. If assembly and pdb are separated in each directory\n"\r
+ + " # dotnet extractor line-number /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
+ 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 "-p":\r
+ case "--pdb":\r
+ pdbPaths = ReadValue().Split(":");\r
+ break;\r
+ case "-o":\r
+ case "--out":\r
+ outputPath = ReadValue();\r
+ break;\r
+ case "-a":\r
+ case "--assembly":\r
+ assemblyPaths = ReadValue().Split(":");\r
+ break;\r
+ default:\r
+ if (arg.Contains("-"))\r
+ {\r
+ UsageLine();\r
+ Console.WriteLine($"Unknown option [{arg}]\n");\r
+ Environment.Exit(0);\r
+ break;\r
+ }\r
+ exceptionPath ??= arg;\r
+ if (!File.Exists(exceptionPath))\r
+ {\r
+ throw new FileNotFoundException("Log file not found\n");\r
+ }\r
+ break;\r
+ }\r
+ }\r
+ if (exceptionPath == null)\r
+ {\r
+ throw new InvalidDataException("Missing exception log path\n");\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
+ 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.ChangeExtension(peFile, "xml");\r
+ if (xmlPath.Contains("/usr/share/dotnet"))\r
+ {\r
+ xmlPath = tempDirectory + "/" + Path.GetFileName(xmlPath);\r
+ }\r
+ GetXmlFromPdb(peFile, pdbFile, xmlPath);\r
+ xmlList.Add(xmlPath);\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ return xmlList;\r
+ }\r
+\r
+ private static void GetXmlFromPdb(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
+ /*\r
+ * Extract\r
+ */\r
+ private static void Extract(Args args, List<string> xmlList)\r
+ {\r
+ string logFile = args.ExceptionPath;\r
+ string outputPath = args.OutputPath;\r
+\r
+ if (xmlList.Count == 0)\r
+ {\r
+ throw new FileNotFoundException("Xml file not found\n");\r
+ }\r
+\r
+ GetLineFromLog(logFile, xmlList, outputPath);\r
+\r
+ if (Directory.Exists(tempDirectory))\r
+ {\r
+ Directory.Delete(tempDirectory, true);\r
+ }\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 void GetLineFromLog(string logPath, List<string> xmlList, string outputPath)\r
+ {\r
+ Console.WriteLine("Extraction result: ");\r
+ try\r
+ {\r
+ using StreamReader fsr = new StreamReader(new FileStream(logPath, FileMode.Open, FileAccess.Read));\r
+ using StreamWriter fsw = new StreamWriter(new FileStream(outputPath, FileMode.Create, FileAccess.Write));\r
+\r
+ bool isParsed = false;\r
+ while (!fsr.EndOfStream)\r
+ {\r
+ string line = fsr.ReadLine();\r
+ if (!line.Contains(" at "))\r
+ {\r
+ continue;\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 assemblyStr = Regex.Match(line, " in (?<splitdata>.*?)\\: ").Groups["splitdata"].Value;\r
+ string tokenStr = Regex.Match(line, " method_token\\((?<splitdata>.*?)\\)\\,").Groups["splitdata"].Value;\r
+ string offsetStr = Regex.Match(line, " il_offset\\((?<splitdata>.*?)\\)").Groups["splitdata"].Value;\r
+ string xmlStr = assemblyStr.Replace(".dll", ".xml");\r
+\r
+ StackTraceInfo stInfo = new StackTraceInfo() { Type = typenameStr, Method = methodStr, Assembly = assemblyStr, Token = tokenStr, Offset = offsetStr/*, Xml = xmlStr*/ };\r
+\r
+ foreach (var xmlPath in xmlList)\r
+ {\r
+ if (xmlPath.Contains(xmlStr))\r
+ {\r
+ isParsed = true;\r
+ GetLineFromXml(xmlPath, stInfo);\r
+ if (stInfo.Filepath == null || stInfo.StartLine == null)\r
+ {\r
+ Console.WriteLine(" ===== PARSE ERROR FOR EXCEPTION LOG IN THIS LINE. PLEASE RECHECK THE EXCEPTION LOG =====");\r
+ break;\r
+ }\r
+ string ret = $" at {stInfo.Type}.{stInfo.Method}({stInfo.Param}) in {stInfo.Filepath}:line {stInfo.StartLine}";\r
+ fsw.WriteLine(ret);\r
+ Console.WriteLine(ret);\r
+ }\r
+ }\r
+ }\r
+ if (!isParsed)\r
+ {\r
+ Console.WriteLine(" There is no content matching the exception log.");\r
+ Console.WriteLine(" Please recheck the assembly and pdb directory path.\n");\r
+ }\r
+ else\r
+ {\r
+ Console.WriteLine($"\nOutput: {outputPath}\n");\r
+ }\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 void ParseEntry(XmlNodeList xn, StackTraceInfo stInfo)\r
+ {\r
+ try\r
+ {\r
+ foreach (XmlNode node in xn)\r
+ {\r
+ if (stInfo.Offset == node.Attributes["offset"].Value)\r
+ {\r
+ if (node.Attributes.Item(1).Name == "startLine")\r
+ {\r
+ stInfo.StartLine = node.Attributes["startLine"].Value;\r
+ }\r
+ if (node.Attributes.Item(3).Name == "endLine")\r
+ {\r
+ stInfo.EndLine = node.Attributes["endLine"].Value;\r
+ }\r
+ stInfo.Document = node.Attributes["document"].Value;\r
+ }\r
+ }\r
+ }\r
+ catch (ArgumentException e)\r
+ {\r
+ Console.WriteLine(e);\r
+ }\r
+ }\r
+ }\r
+}\r