1 using Microsoft.DiaSymReader.Tools;
\r
3 using System.Collections.Generic;
\r
6 using System.Text.RegularExpressions;
\r
9 namespace Tizen.Runtime.Tools
\r
11 public class LineNumber
\r
13 private static readonly string tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
\r
19 internal sealed class Args
\r
21 public readonly string[] AssemblyPaths;
\r
22 public readonly string[] PdbPaths;
\r
23 public readonly string ExceptionPath;
\r
24 public readonly string OutputPath;
\r
26 public Args(string[] assemblyPaths, string[] pdbPaths, string exceptionPath, string outputPath)
\r
28 AssemblyPaths = assemblyPaths;
\r
29 PdbPaths = pdbPaths;
\r
30 ExceptionPath = exceptionPath;
\r
31 OutputPath = outputPath;
\r
35 public static void UsageLine()
\r
37 string UsageMsg = "Usage: dotnet-extractor line-number [Log] [Options]\n\n"
\r
39 + "<Filepath> Path to the exception log file (Filename extension: xxxxx.log)\n\n"
\r
40 //+ "<String> Input the log message directly\n\n"
\r
42 + " -h, --help Show this help message\n"
\r
43 + " -a, --assembly <Path1:Path2:...> Multiple paths with assembly directories separated by colon(':')\n"
\r
44 + " -p, --pdb <Pdb path> Path to the pdb directory (Can be omitted if it is the same as the assembly directory path)\n"
\r
45 + " -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
47 + "1. If both assembly and pdb are in the current directory\n"
\r
48 + " # dotnet extractor line-number /tmp/Exception1.log\n"
\r
49 + "2. If both assembly and pdb are in the same directory specified\n"
\r
50 + " # dotnet extractor line-number /tmp/Exception2.log --assembly /opt/usr/globalapps/org.tizen.example.TestApp/:/usr/share/dotnet.tizen/\n"
\r
51 + "3. If assembly and pdb are separated in each directory\n"
\r
52 + " # dotnet extractor line-number /tmp/Exception3.log --assembly /usr/share/dotnet.tizen/framework/ --pdb /tmp/pdbs/\n";
\r
53 Console.WriteLine(UsageMsg);
\r
56 public void LineNumbers(string[] args)
\r
60 Args parsedArgs = ParseArgs(args);
\r
61 Extract(parsedArgs, Convert(parsedArgs));
\r
66 Console.Error.WriteLine(e.Message);
\r
70 internal static Args ParseArgs(string[] args)
\r
72 string[] assemblyPaths = null;
\r
73 string[] pdbPaths = null;
\r
74 string exceptionPath = null;
\r
75 string outputPath = null;
\r
78 while (i < args.Length)
\r
80 var arg = args[i++];
\r
81 string ReadValue() => (i < args.Length) ? args[i++] : throw new InvalidDataException(string.Format("Missing value for option '{0}'", arg));
\r
87 Environment.Exit(0);
\r
91 pdbPaths = ReadValue().Split(":");
\r
95 outputPath = ReadValue();
\r
99 assemblyPaths = ReadValue().Split(":");
\r
102 if (arg.Contains("-"))
\r
105 Console.WriteLine($"Unknown option [{arg}]\n");
\r
106 Environment.Exit(0);
\r
109 exceptionPath ??= arg;
\r
110 if (!File.Exists(exceptionPath))
\r
112 throw new FileNotFoundException("Log file not found\n");
\r
117 if (exceptionPath == null)
\r
119 throw new InvalidDataException("Missing exception log path\n");
\r
121 assemblyPaths ??= new string[] { Directory.GetCurrentDirectory() };
\r
122 pdbPaths ??= assemblyPaths;
\r
125 outputPath ??= Path.ChangeExtension(exceptionPath, "out");
\r
127 catch (Exception e)
\r
129 throw new InvalidDataException(e.Message);
\r
133 assemblyPaths: assemblyPaths,
\r
134 pdbPaths: pdbPaths,
\r
135 exceptionPath: exceptionPath,
\r
136 outputPath: outputPath);
\r
139 private static List<string> GrabFiles(string[] paths, string searchPattern)
\r
141 List<string> files = new List<string>();
\r
142 foreach (var assemDir in paths)
\r
144 if (Directory.Exists(assemDir))
\r
146 foreach (var peFile in Directory.GetFiles(assemDir, searchPattern, SearchOption.AllDirectories))
\r
158 private static List<string> Convert(Args args)
\r
160 List<string> peFiles = GrabFiles(args.AssemblyPaths, "*.dll");
\r
161 if (peFiles.Count == 0)
\r
163 throw new FileNotFoundException("Assembly file not found\n");
\r
166 List<string> pdbFiles = GrabFiles(args.PdbPaths, "*.pdb");
\r
167 if (pdbFiles.Count == 0)
\r
169 throw new FileNotFoundException("PDB file not found\n");
\r
172 Console.Write("Converting pdb to xml...");
\r
173 Console.SetCursorPosition(0, Console.CursorTop);
\r
175 Directory.CreateDirectory(tempDirectory);
\r
177 List<string> xmlList = new List<string>();
\r
178 foreach (var pdbFile in pdbFiles)
\r
180 foreach (var peFile in peFiles)
\r
182 if (Path.GetFileNameWithoutExtension(peFile) == Path.GetFileNameWithoutExtension(pdbFile))
\r
184 string xmlPath = Path.ChangeExtension(peFile, "xml");
\r
185 if (xmlPath.Contains("/usr/share/dotnet"))
\r
187 xmlPath = tempDirectory + "/" + Path.GetFileName(xmlPath);
\r
189 GetXmlFromPdb(peFile, pdbFile, xmlPath);
\r
190 xmlList.Add(xmlPath);
\r
198 private static void GetXmlFromPdb(string assemblyPath, string pdbPath, string xmlPath)
\r
200 using var peStream = new FileStream(assemblyPath, FileMode.Open, FileAccess.Read);
\r
201 using var pdbStream = new FileStream(pdbPath, FileMode.Open, FileAccess.Read);
\r
202 using var dstFileStream = new FileStream(xmlPath, FileMode.Create, FileAccess.ReadWrite);
\r
203 using var sw = new StreamWriter(dstFileStream, Encoding.UTF8);
\r
204 PdbToXmlOptions options = PdbToXmlOptions.ResolveTokens | PdbToXmlOptions.IncludeModuleDebugInfo | PdbToXmlOptions.IncludeTokens;
\r
206 PdbToXmlConverter.ToXml(sw, pdbStream, peStream, options);
\r
212 private static void Extract(Args args, List<string> xmlList)
\r
214 string logFile = args.ExceptionPath;
\r
215 string outputPath = args.OutputPath;
\r
217 if (xmlList.Count == 0)
\r
219 throw new FileNotFoundException("Xml file not found\n");
\r
222 GetLineFromLog(logFile, xmlList, outputPath);
\r
224 if (Directory.Exists(tempDirectory))
\r
226 Directory.Delete(tempDirectory, true);
\r
230 internal sealed class StackTraceInfo
\r
232 public string Type;
\r
233 public string Method;
\r
234 public string Param;
\r
235 public string Assembly;
\r
236 public string Token;
\r
237 public string Offset;
\r
238 public string Document;
\r
239 public string Filepath;
\r
240 public string Filename;
\r
241 public string StartLine;
\r
242 public string EndLine;
\r
245 private static void GetLineFromLog(string logPath, List<string> xmlList, string outputPath)
\r
247 Console.WriteLine("Extraction result: ");
\r
250 using StreamReader fsr = new StreamReader(new FileStream(logPath, FileMode.Open, FileAccess.Read));
\r
251 using StreamWriter fsw = new StreamWriter(new FileStream(outputPath, FileMode.Create, FileAccess.Write));
\r
253 bool isParsed = false;
\r
254 while (!fsr.EndOfStream)
\r
256 string line = fsr.ReadLine();
\r
257 if (!line.Contains(" at "))
\r
261 string typeMethodStr = Regex.Match(line, " at (?<splitdata>.*?)\\(\\)").Groups["splitdata"].Value;
\r
262 string methodStr = typeMethodStr.Split(".")[^1];
\r
263 string typenameStr = typeMethodStr.Replace("." + methodStr, "");
\r
264 string assemblyStr = Regex.Match(line, " in (?<splitdata>.*?)\\: ").Groups["splitdata"].Value;
\r
265 string tokenStr = Regex.Match(line, " method_token\\((?<splitdata>.*?)\\)\\,").Groups["splitdata"].Value;
\r
266 string offsetStr = Regex.Match(line, " il_offset\\((?<splitdata>.*?)\\)").Groups["splitdata"].Value;
\r
267 string xmlStr = assemblyStr.Replace(".dll", ".xml");
\r
269 StackTraceInfo stInfo = new StackTraceInfo() { Type = typenameStr, Method = methodStr, Assembly = assemblyStr, Token = tokenStr, Offset = offsetStr/*, Xml = xmlStr*/ };
\r
271 foreach (var xmlPath in xmlList)
\r
273 if (xmlPath.Contains(xmlStr))
\r
276 GetLineFromXml(xmlPath, stInfo);
\r
277 if (stInfo.Filepath == null || stInfo.StartLine == null)
\r
279 Console.WriteLine(" ===== PARSE ERROR FOR EXCEPTION LOG IN THIS LINE. PLEASE RECHECK THE EXCEPTION LOG =====");
\r
282 string ret = $" at {stInfo.Type}.{stInfo.Method}({stInfo.Param}) in {stInfo.Filepath}:line {stInfo.StartLine}";
\r
283 fsw.WriteLine(ret);
\r
284 Console.WriteLine(ret);
\r
290 Console.WriteLine(" There is no content matching the exception log.");
\r
291 Console.WriteLine(" Please recheck the assembly and pdb directory path.\n");
\r
295 Console.WriteLine($"\nOutput: {outputPath}\n");
\r
298 catch (Exception e)
\r
300 Console.WriteLine(e);
\r
304 private static void GetLineFromXml(string xmlPath, StackTraceInfo stInfo)
\r
308 XmlDocument xmlDoc = new XmlDocument();
\r
309 xmlDoc.Load(xmlPath);
\r
310 XmlElement xRoot = xmlDoc.DocumentElement;
\r
311 XmlNodeList xnList = xRoot.ChildNodes;
\r
312 int xnCount = xnList.Count;
\r
315 for (int i = xnCount - 1; i >= 0; i--)
\r
317 XmlNode node = xnList[i];
\r
318 if (node.Name == "files")
\r
320 ParseFile(node.ChildNodes, stInfo);
\r
322 else if (node.Name == "methods")
\r
324 ParseMethod(node.ChildNodes, stInfo);
\r
329 catch (ArgumentException e)
\r
331 Console.WriteLine(e);
\r
335 private static void ParseFile(XmlNodeList xn, StackTraceInfo stInfo)
\r
339 foreach (XmlNode node in xn)
\r
341 if (stInfo.Document == node.Attributes["id"].Value)
\r
343 stInfo.Filepath = node.Attributes["name"].Value;
\r
344 stInfo.Filename = Path.GetFileName(node.Attributes["name"].Value);
\r
348 catch (ArgumentException e)
\r
350 Console.WriteLine(e);
\r
354 private static void ParseMethod(XmlNodeList xn, StackTraceInfo stInfo)
\r
358 foreach (XmlNode node in xn)
\r
360 if (stInfo.Type == node.Attributes["containingType"].Value &&
\r
361 stInfo.Method == node.Attributes["name"].Value &&
\r
362 stInfo.Token == node.Attributes["token"].Value)
\r
364 if (node.Attributes.Item(2).Name == "parameterNames")
\r
366 stInfo.Param = node.Attributes["parameterNames"].Value;
\r
368 ParseSequence(node.ChildNodes, stInfo);
\r
372 catch (ArgumentException e)
\r
374 Console.WriteLine(e);
\r
378 private static void ParseSequence(XmlNodeList xn, StackTraceInfo stInfo)
\r
382 foreach (XmlNode node in xn)
\r
384 if (node.Name == "sequencePoints")
\r
386 ParseEntry(node.ChildNodes, stInfo);
\r
390 catch (ArgumentException e)
\r
392 Console.WriteLine(e);
\r
396 private static void ParseEntry(XmlNodeList xn, StackTraceInfo stInfo)
\r
400 foreach (XmlNode node in xn)
\r
402 if (stInfo.Offset == node.Attributes["offset"].Value)
\r
404 if (node.Attributes.Item(1).Name == "startLine")
\r
406 stInfo.StartLine = node.Attributes["startLine"].Value;
\r
408 if (node.Attributes.Item(3).Name == "endLine")
\r
410 stInfo.EndLine = node.Attributes["endLine"].Value;
\r
412 stInfo.Document = node.Attributes["document"].Value;
\r
416 catch (ArgumentException e)
\r
418 Console.WriteLine(e);
\r