### Usage
```
-dotnet-extractor line-number [Log] [Options]
+dotnet-extractor [Commands]
```
-#### Log
-* Filepath
- Path to the exception log file (Filename extension: xxxxx.log)
+```
+dotnet-extractor convert [Options]
+```
+
+#### Commands
+* convert
+ Get the line number from the token value in the stacktrace
+
+#### Interactive shell
+* Input string
+ Input the exception log string directly
#### Options
* -h, --help
Show this help message
+* -i, --input [Input path]
+ Path to the exception log file (File extension: xxxxx.log)
+
* -a, --assembly [Path1:Path2:...]
Multiple paths with assembly directories separated by colon(':')
* -p, --pdb [Pdb path]
Path to the pdb directory (Can be omitted if it is the same as the assembly directory path)
-* -o, --out [Output path]
+* -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)
#### Example
+* If you enter the exception log string directly
+```
+ # dotnet extractor convert
+ ### Dotnet Extractor Tool (v1.0) ###
+ Enter the exception log string:
+ I/DOTNET_LAUNCHER(12345): at TestApp.Program.TestMethod1() in TestApp.Tizen.dll: token 0x6000001+0x5
+ I/DOTNET_LAUNCHER(12345): at TestApp.Program.OnCreate() in TestApp.Tizen.dll: token 0x6000002+0x1
+```
+
* If both assembly and pdb are in the current directory
```
- # dotnet extractor line-number /tmp/Exception1.log
+ # dotnet extractor convert --input /tmp/Exception1.log
```
* If both assembly and pdb are in the same directory specified
```
- # dotnet extractor /tmp/Exception2.log --assembly /opt/usr/globalapps/org.tizen.example.TestApp/:/usr/share/dotnet.tizen/
+ # dotnet extractor convert --input /tmp/Exception2.log --assembly /opt/usr/globalapps/org.tizen.example.TestApp.Tizen/:/usr/share/dotnet.tizen/
```
* If assembly and pdb are separated in each directory
```
- # dotnet extractor /tmp/Exception3.log --assembly /usr/share/dotnet.tizen/framework/ --pdb /tmp/pdbs/
+ # dotnet extractor convert --input /tmp/Exception3.log --assembly /usr/share/dotnet.tizen/framework/ --pdb /tmp/pdbs/
```
#### Others
* Log format
```
- I/DOTNET_LAUNCHER(27298): System.NullReferenceException: Object reference not set to an instance of an object.
- I/DOTNET_LAUNCHER(27298): at TestApp.Program.TestMethod() in TestApp.dll: method_token(0x6000001), il_offset(0x5)
- I/DOTNET_LAUNCHER(27298): at TestApp.Program.OnCreate() in TestApp.dll: method_token(0x6000002), il_offset(0x1)
+ I/DOTNET_LAUNCHER(12345): System.NullReferenceException: Object reference not set to an instance of an object.
+ I/DOTNET_LAUNCHER(12345): at TestApp.Program.TestMethod1() in TestApp.Tizen.dll: token 0x6000001+0x5
+ I/DOTNET_LAUNCHER(12345): at TestApp.Program.OnCreate() in TestApp.Tizen.dll: token 0x6000002+0x1
------- Format -------
- I/DOTNET_LAUNCHER(00000): at {typeName}.{methodName}() in {moduleName}: method_token{methodToken}, il_offset{ilOffset}
+ I/DOTNET_LAUNCHER(00000): at {typeName}.{methodName}({parameters}) in {moduleName}: token {methodToken}+{ilOffset}
```
----
#### Sample
```
sh-3.2# cat /tmp/Exception.log
-I/DOTNET_LAUNCHER(27298): System.NullReferenceException: Object reference not set to an instance of an object.
-I/DOTNET_LAUNCHER(27298): at LineNumberExtract.Program.ExceptionMethod2() in LineNumberExtract.Tizen.dll: method_token(0x6000001), il_offset(0x5)
-I/DOTNET_LAUNCHER(27298): at LineNumberExtract.Program.ExceptionMethod1() in LineNumberExtract.Tizen.dll: method_token(0x6000002), il_offset(0x1)
-I/DOTNET_LAUNCHER(27298): at LineNumberExtract.Program.OnCreate() in LineNumberExtract.Tizen.dll: method_token(0x6000004), il_offset(0x15)
+I/DOTNET_LAUNCHER(12345): System.NullReferenceException: Object reference not set to an instance of an object.
+I/DOTNET_LAUNCHER(12345): at TestApp.Program.TestMethod1() in TestApp.Tizen.dll: token 0x6000001+0x5
+I/DOTNET_LAUNCHER(12345): at TestApp.Program.TestMethod2() in TestApp.Tizen.dll: token 0x6000002+0x1
+I/DOTNET_LAUNCHER(12345): at TestApp.Program.OnCreate() in TestApp.Tizen.dll: token 0x6000004+0x15
sh-3.2#
-sh-3.2# dotnet eExtractor /tmp/Exception.log --assembly /opt/usr/globalapps/org.tizen.example.LineNumberExtract.Tizen/
+sh-3.2# dotnet extractor convert --input /tmp/Exception.log --assembly /opt/usr/globalapps/org.tizen.example.TestApp.Tizen/
##### Line Number Extractor Tool (v1.0) #####
Extraction result:
- at LineNumberExtract.Program.ExceptionMethod2(i) in U:\PTX\LineNumberExtract\LineNumberExtract\LineNumberExtract.Tizen\LineNumberExtract.Tizen.cs:line 14
- at LineNumberExtract.Program.ExceptionMethod1(t) in U:\PTX\LineNumberExtract\LineNumberExtract\LineNumberExtract.Tizen\LineNumberExtract.Tizen.cs:line 19
- at LineNumberExtract.Program.OnCreate() in U:\PTX\LineNumberExtract\LineNumberExtract\LineNumberExtract.Tizen\LineNumberExtract.Tizen.cs:line 58
+ at TestApp.Program.TestMethod1() in U:\TestApp\TestApp\TestApp.Tizen\TestApp.Tizen.cs:line 14
+ at TestApp.Program.TestMethod2() in U:\TestApp\TestApp\TestApp.Tizen\TestApp.Tizen.cs:line 19
+ at TestApp.Program.OnCreate() in U:\TestApp\TestApp\TestApp.Tizen\TestApp.Tizen.cs:line 58
Output: /tmp/Exception.out
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
{\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 List<string> ExceptionString;\r
public readonly string OutputPath;\r
\r
- public Args(string[] assemblyPaths, string[] pdbPaths, string exceptionPath, string outputPath)\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 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
+ 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, --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
+ + " -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 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
+ + "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
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
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 "--out":\r
+ case "--output":\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
+ UsageLine();\r
+ Console.WriteLine($"Unknown option [{arg}]\n");\r
+ Environment.Exit(0);\r
break;\r
}\r
}\r
if (exceptionPath == null)\r
{\r
- throw new InvalidDataException("Missing exception log path\n");\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
assemblyPaths: assemblyPaths,\r
pdbPaths: pdbPaths,\r
exceptionPath: exceptionPath,\r
+ exceptionString: exceptionString,\r
outputPath: outputPath);\r
}\r
\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
+ string xmlPath = Path.Combine(tempDirectory, Path.GetFileName(Path.ChangeExtension(peFile, "xml")));\r
+ GenXmlFromPdb(peFile, pdbFile, xmlPath);\r
xmlList.Add(xmlPath);\r
break;\r
}\r
return xmlList;\r
}\r
\r
- private static void GetXmlFromPdb(string assemblyPath, string pdbPath, string xmlPath)\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
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
- string logFile = args.ExceptionPath;\r
- string outputPath = args.OutputPath;\r
-\r
if (xmlList.Count == 0)\r
{\r
+ RemoveTempDirectory();\r
throw new FileNotFoundException("Xml file not found\n");\r
}\r
\r
- GetLineFromLog(logFile, xmlList, outputPath);\r
+ GetLineFromLog(args, xmlList);\r
\r
- if (Directory.Exists(tempDirectory))\r
- {\r
- Directory.Delete(tempDirectory, true);\r
- }\r
+ RemoveTempDirectory();\r
}\r
\r
internal sealed class StackTraceInfo\r
public string EndLine;\r
}\r
\r
- private static void GetLineFromLog(string logPath, List<string> xmlList, string outputPath)\r
+ private static string GetRegex(string line, List<string> xmlList)\r
{\r
- Console.WriteLine("Extraction result: ");\r
- try\r
+ string ret = line;\r
+ string logtagStr = Regex.Match(line, "(?<splitdata>.*?) at").Groups["splitdata"].Value;\r
+ if (logtagStr.Length != 0)\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
+ 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
- bool isParsed = false;\r
- while (!fsr.EndOfStream)\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
- string line = fsr.ReadLine();\r
- if (!line.Contains(" at "))\r
+ GetLineFromXml(xmlPath, stInfo);\r
+ if (stInfo.Filepath != null && stInfo.StartLine != null)\r
{\r
- continue;\r
+ ret = $" at {stInfo.Type}.{stInfo.Method}({parameterStr}) in {stInfo.Filepath}:line {stInfo.StartLine}";\r
+ break;\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
+ }\r
+ return ret;\r
+ }\r
\r
- StackTraceInfo stInfo = new StackTraceInfo() { Type = typenameStr, Method = methodStr, Assembly = assemblyStr, Token = tokenStr, Offset = offsetStr/*, Xml = xmlStr*/ };\r
+ private static void GetLineFromLog(Args args, List<string> xmlList)\r
+ {\r
+ Console.WriteLine("Extraction result: ");\r
\r
- foreach (var xmlPath in xmlList)\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 (xmlPath.Contains(xmlStr))\r
+ if (!line.Contains("at "))\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
+ continue;\r
}\r
+ string ret = GetRegex(line, xmlList);\r
+ result.Add(ret);\r
+ Console.WriteLine(ret);\r
}\r
}\r
- if (!isParsed)\r
+ else\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
+ // <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
- else\r
+\r
+ string output = string.Empty;\r
+ if (args.OutputPath != null)\r
{\r
- Console.WriteLine($"\nOutput: {outputPath}\n");\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
}\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 (stInfo.Offset == node.Attributes["offset"].Value)\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
- 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
+ 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