1 using Microsoft.DiaSymReader.Tools;
\r
3 using System.Collections.Generic;
\r
4 using System.Globalization;
\r
7 using System.Text.RegularExpressions;
\r
10 namespace Tizen.Runtime.Tools
\r
12 public class LineNumber
\r
14 private static readonly string tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
\r
16 internal sealed class Args
\r
18 public readonly string[] AssemblyPaths;
\r
19 public readonly string[] PdbPaths;
\r
20 public readonly string ExceptionPath;
\r
21 public readonly List<string> ExceptionString;
\r
22 public readonly string OutputPath;
\r
24 public Args(string[] assemblyPaths, string[] pdbPaths, string exceptionPath, List<string> exceptionString, string outputPath)
\r
26 AssemblyPaths = assemblyPaths;
\r
27 PdbPaths = pdbPaths;
\r
28 ExceptionPath = exceptionPath;
\r
29 ExceptionString = exceptionString;
\r
30 OutputPath = outputPath;
\r
34 public static void UsageLine()
\r
36 string UsageMsg = "Usage: dotnet-extractor convert [Options]\n\n"
\r
37 + "Interactive shell:\n"
\r
38 + "<Input string> Input the exception log string directly\n\n"
\r
40 + " -h, --help Show this help message\n"
\r
41 + " -i, --input <Input path> Path to the exception log file (File extension: xxxxx.log)\n"
\r
42 + " -a, --assembly <Path1:Path2:...> Multiple paths with assembly directories separated by colon(':')\n"
\r
43 + " -p, --pdb <Pdb path> Path to the pdb directory (Can be omitted if it is the same as the assembly directory path)\n"
\r
44 + " -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
46 + "1. If you enter the exception log string directly\n"
\r
47 + " # dotnet extractor convert\n"
\r
48 + " Enter the exception log string:\n"
\r
49 + " I/DOTNET_LAUNCHER(12345): at TestApp.Program.TestMethod() in TestApp.Tizen.dll: token 0x6000001+0x5\n"
\r
50 + " I/DOTNET_LAUNCHER(12345): at TestApp.Program.OnCreate() in TestApp.Tizen.dll: token 0x6000002+0x1\n\n"
\r
51 + "2. If both assembly and pdb are in the current directory\n"
\r
52 + " # dotnet extractor convert --input /tmp/Exception1.log\n\n"
\r
53 + "3. If both assembly and pdb are in the same directory specified\n"
\r
54 + " # dotnet extractor convert --input /tmp/Exception2.log --assembly /opt/usr/globalapps/org.tizen.example.TestApp.Tizen/:/usr/share/dotnet.tizen/\n\n"
\r
55 + "4. If assembly and pdb are separated in each directory\n"
\r
56 + " # dotnet extractor convert --input /tmp/Exception3.log --assembly /usr/share/dotnet.tizen/framework/ --pdb /tmp/pdbs/\n";
\r
57 Console.WriteLine(UsageMsg);
\r
60 public void LineNumbers(string[] args)
\r
64 Args parsedArgs = ParseArgs(args);
\r
65 Extract(parsedArgs, Convert(parsedArgs));
\r
70 Console.Error.WriteLine(e.Message);
\r
74 internal static Args ParseArgs(string[] args)
\r
76 string[] assemblyPaths = null;
\r
77 string[] pdbPaths = null;
\r
78 string exceptionPath = null;
\r
79 List<string> exceptionString = new List<string>();
\r
80 string outputPath = null;
\r
83 while (i < args.Length)
\r
85 var arg = args[i++];
\r
86 string ReadValue() => (i < args.Length) ? args[i++] : throw new InvalidDataException(string.Format("Missing value for option '{0}'", arg));
\r
92 Environment.Exit(0);
\r
96 exceptionPath = ReadValue();
\r
100 assemblyPaths = ReadValue().Split(":");
\r
104 pdbPaths = ReadValue().Split(":");
\r
108 outputPath = ReadValue();
\r
112 Console.WriteLine($"Unknown option [{arg}]\n");
\r
113 Environment.Exit(0);
\r
117 if (exceptionPath == null)
\r
119 Console.WriteLine("Enter the exception log string:");
\r
121 while ((line = Console.ReadLine()) != null && line != "")
\r
123 if (!line.Contains(":"))
\r
125 exceptionPath = line;
\r
126 exceptionString.Clear();
\r
129 exceptionString.Add(line);
\r
132 if (exceptionString.Count == 0)
\r
134 if (exceptionPath == null)
\r
136 throw new InvalidDataException("Missing exception log path or log string\n");
\r
138 else if (!File.Exists(exceptionPath))
\r
140 throw new FileNotFoundException("Exception log file not found\n");
\r
144 assemblyPaths ??= new string[] { Directory.GetCurrentDirectory() };
\r
145 pdbPaths ??= assemblyPaths;
\r
148 outputPath ??= Path.ChangeExtension(exceptionPath, "out");
\r
150 catch (Exception e)
\r
152 throw new InvalidDataException(e.Message);
\r
156 assemblyPaths: assemblyPaths,
\r
157 pdbPaths: pdbPaths,
\r
158 exceptionPath: exceptionPath,
\r
159 exceptionString: exceptionString,
\r
160 outputPath: outputPath);
\r
163 private static List<string> GrabFiles(string[] paths, string searchPattern)
\r
165 List<string> files = new List<string>();
\r
166 foreach (var assemDir in paths)
\r
168 if (Directory.Exists(assemDir))
\r
170 foreach (var peFile in Directory.GetFiles(assemDir, searchPattern, SearchOption.AllDirectories))
\r
182 private static List<string> Convert(Args args)
\r
184 List<string> peFiles = GrabFiles(args.AssemblyPaths, "*.dll");
\r
185 if (peFiles.Count == 0)
\r
187 throw new FileNotFoundException("Assembly file not found\n");
\r
190 List<string> pdbFiles = GrabFiles(args.PdbPaths, "*.pdb");
\r
191 if (pdbFiles.Count == 0)
\r
193 throw new FileNotFoundException("PDB file not found\n");
\r
196 Console.Write("Converting pdb to xml...");
\r
197 Console.SetCursorPosition(0, Console.CursorTop);
\r
199 Directory.CreateDirectory(tempDirectory);
\r
201 List<string> xmlList = new List<string>();
\r
202 foreach (var pdbFile in pdbFiles)
\r
204 foreach (var peFile in peFiles)
\r
206 if (Path.GetFileNameWithoutExtension(peFile) == Path.GetFileNameWithoutExtension(pdbFile))
\r
208 string xmlPath = Path.Combine(tempDirectory, Path.GetFileName(Path.ChangeExtension(peFile, "xml")));
\r
209 GenXmlFromPdb(peFile, pdbFile, xmlPath);
\r
210 xmlList.Add(xmlPath);
\r
218 private static void GenXmlFromPdb(string assemblyPath, string pdbPath, string xmlPath)
\r
220 using var peStream = new FileStream(assemblyPath, FileMode.Open, FileAccess.Read);
\r
221 using var pdbStream = new FileStream(pdbPath, FileMode.Open, FileAccess.Read);
\r
222 using var dstFileStream = new FileStream(xmlPath, FileMode.Create, FileAccess.ReadWrite);
\r
223 using var sw = new StreamWriter(dstFileStream, Encoding.UTF8);
\r
224 PdbToXmlOptions options = PdbToXmlOptions.ResolveTokens | PdbToXmlOptions.IncludeModuleDebugInfo | PdbToXmlOptions.IncludeTokens;
\r
226 PdbToXmlConverter.ToXml(sw, pdbStream, peStream, options);
\r
229 private static void RemoveTempDirectory()
\r
231 if (Directory.Exists(tempDirectory))
\r
233 Directory.Delete(tempDirectory, true);
\r
240 private static void Extract(Args args, List<string> xmlList)
\r
242 if (xmlList.Count == 0)
\r
244 RemoveTempDirectory();
\r
245 throw new FileNotFoundException("Xml file not found\n");
\r
248 GetLineFromLog(args, xmlList);
\r
250 RemoveTempDirectory();
\r
253 internal sealed class StackTraceInfo
\r
255 public string Type;
\r
256 public string Method;
\r
257 public string Param;
\r
258 public string Assembly;
\r
259 public string Token;
\r
260 public string Offset;
\r
261 public string Document;
\r
262 public string Filepath;
\r
263 public string Filename;
\r
264 public string StartLine;
\r
265 public string EndLine;
\r
268 private static string GetRegex(string line, List<string> xmlList)
\r
271 string logtagStr = Regex.Match(line, "(?<splitdata>.*?) at").Groups["splitdata"].Value;
\r
272 if (logtagStr.Length != 0)
\r
274 ret = line.Replace(logtagStr, "");
\r
276 string typeMethodStr = Regex.Match(line, "at (?<splitdata>.*?)\\((.*)\\)").Groups["splitdata"].Value;
\r
277 string methodStr = typeMethodStr.Split(".")[^1];
\r
278 string typenameStr = typeMethodStr.Replace("." + methodStr, "");
\r
279 string parameterStr = Regex.Match(line, methodStr + "\\((?<splitdata>.*?)\\)").Groups["splitdata"].Value;
\r
280 string assemblyStr = Regex.Match(line, " in (?<splitdata>.*?)\\: ").Groups["splitdata"].Value;
\r
281 string[] tokenOffsetStr = Regex.Match(line, "\\: token (?<splitdata>.*)?").Groups["splitdata"].Value.Split("+");
\r
282 string xmlStr = assemblyStr.Contains(".ni.dll") ? assemblyStr.Replace(".ni.dll", ".xml") : assemblyStr.Replace(".dll", ".xml");
\r
284 if (tokenOffsetStr.Length != 2 || methodStr == "" || typenameStr == "" || assemblyStr == "")
\r
289 StackTraceInfo stInfo = new StackTraceInfo() { Type = typenameStr, Method = methodStr, Assembly = assemblyStr, Token = tokenOffsetStr[0], Offset = tokenOffsetStr[1] };
\r
291 foreach (var xmlPath in xmlList)
\r
293 if (xmlPath.Contains(xmlStr))
\r
295 GetLineFromXml(xmlPath, stInfo);
\r
296 if (stInfo.Filepath != null && stInfo.StartLine != null)
\r
298 ret = $" at {stInfo.Type}.{stInfo.Method}({parameterStr}) in {stInfo.Filepath}:line {stInfo.StartLine}";
\r
306 private static void GetLineFromLog(Args args, List<string> xmlList)
\r
308 Console.WriteLine("Extraction result: ");
\r
312 List<string> result = new List<string>();
\r
313 if (args.ExceptionPath == null)
\r
315 // <Exception String>
\r
316 foreach (var line in args.ExceptionString)
\r
318 if (!line.Contains("at "))
\r
322 string ret = GetRegex(line, xmlList);
\r
324 Console.WriteLine(ret);
\r
329 // <Exception Path>
\r
330 using StreamReader fsr = new StreamReader(new FileStream(args.ExceptionPath, FileMode.Open, FileAccess.Read));
\r
331 while (!fsr.EndOfStream)
\r
333 string line = fsr.ReadLine();
\r
334 if (!line.Contains(" at "))
\r
338 string ret = GetRegex(line, xmlList);
\r
340 Console.WriteLine(ret);
\r
344 string output = string.Empty;
\r
345 if (args.OutputPath != null)
\r
347 using StreamWriter fsw = new StreamWriter(new FileStream(args.OutputPath, FileMode.Create, FileAccess.Write));
\r
348 foreach (var ret in result)
\r
350 fsw.WriteLine(ret);
\r
352 output = $"\nOutput: {args.OutputPath}\n";
\r
354 Console.WriteLine($"{output}");
\r
356 catch (Exception e)
\r
358 Console.WriteLine(e);
\r
362 private static void GetLineFromXml(string xmlPath, StackTraceInfo stInfo)
\r
366 XmlDocument xmlDoc = new XmlDocument();
\r
367 xmlDoc.Load(xmlPath);
\r
368 XmlElement xRoot = xmlDoc.DocumentElement;
\r
369 XmlNodeList xnList = xRoot.ChildNodes;
\r
370 int xnCount = xnList.Count;
\r
373 for (int i = xnCount - 1; i >= 0; i--)
\r
375 XmlNode node = xnList[i];
\r
376 if (node.Name == "files")
\r
378 ParseFile(node.ChildNodes, stInfo);
\r
380 else if (node.Name == "methods")
\r
382 ParseMethod(node.ChildNodes, stInfo);
\r
387 catch (ArgumentException e)
\r
389 Console.WriteLine(e);
\r
393 private static void ParseFile(XmlNodeList xn, StackTraceInfo stInfo)
\r
397 foreach (XmlNode node in xn)
\r
399 if (stInfo.Document == node.Attributes["id"].Value)
\r
401 stInfo.Filepath = node.Attributes["name"].Value;
\r
402 stInfo.Filename = Path.GetFileName(node.Attributes["name"].Value);
\r
406 catch (ArgumentException e)
\r
408 Console.WriteLine(e);
\r
412 private static void ParseMethod(XmlNodeList xn, StackTraceInfo stInfo)
\r
416 foreach (XmlNode node in xn)
\r
418 if (stInfo.Type == node.Attributes["containingType"].Value &&
\r
419 stInfo.Method == node.Attributes["name"].Value &&
\r
420 stInfo.Token == node.Attributes["token"].Value)
\r
422 if (node.Attributes.Item(2).Name == "parameterNames")
\r
424 stInfo.Param = node.Attributes["parameterNames"].Value;
\r
426 ParseSequence(node.ChildNodes, stInfo);
\r
430 catch (ArgumentException e)
\r
432 Console.WriteLine(e);
\r
436 private static void ParseSequence(XmlNodeList xn, StackTraceInfo stInfo)
\r
440 foreach (XmlNode node in xn)
\r
442 if (node.Name == "sequencePoints")
\r
444 ParseEntry(node.ChildNodes, stInfo);
\r
448 catch (ArgumentException e)
\r
450 Console.WriteLine(e);
\r
454 private static int HexToInt(string value)
\r
456 // strip the leading 0x
\r
457 if (value.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
\r
459 value = value.Substring(2);
\r
461 return Int32.Parse(value, NumberStyles.HexNumber);
\r
464 private static void ParseEntry(XmlNodeList xn, StackTraceInfo stInfo)
\r
468 XmlNode bestPointSoFar = null;
\r
469 int ilOffset = HexToInt(stInfo.Offset);
\r
470 foreach (XmlNode node in xn)
\r
472 // If the attribute is not 'startLine', but 'hidden', select the best value so far
\r
473 if (HexToInt(node.Attributes["offset"].Value) > ilOffset)
\r
477 if (node.Attributes["startLine"] != null)
\r
479 bestPointSoFar = node;
\r
482 if (bestPointSoFar != null)
\r
484 stInfo.StartLine = bestPointSoFar.Attributes["startLine"].Value;
\r
485 stInfo.EndLine = bestPointSoFar.Attributes["endLine"].Value;
\r
486 stInfo.Document = bestPointSoFar.Attributes["document"].Value;
\r
489 catch (ArgumentException e)
\r
491 Console.WriteLine(e);
\r