internal static extern bool SymGetLineFromInlineContextW( IntPtr hProcess, Int64 dwAddr, Int32 InlineContext, Int64 qwModuleBaseAddress, out Int32 pdwDisplacement, [MarshalAs(UnmanagedType.LPStruct)] IMAGEHLP_LINEW64 Line);
// If the IMAGEHLP_LINEW64 represents a line of Principia code, returns a // GitHub link. Otherwise, returns null. // If snippets is true, emits a raw link without Markdown formatting; // within the Principia repository, this will be turned into a snippet by // GitHub: https://help.github.com/articles/creating-a-permanent-link-to-a-code-snippet/. private static string ParseLine(IntPtr handle, IMAGEHLP_LINEW64 line, SYMBOL_INFOW symbol, string commit, bool snippets) { var file_regex = new Regex(@".*\\[Pp]rincipia\\([a-z_]+)\\(\S+)"); Match file_match = file_regex.Match(line.FileName); if (!file_match.Success) { return(null); } string file = $"{file_match.Groups[1]}/{file_match.Groups[2]}"; int line_number = line.LineNumber; int? start_line_number = line.LineNumber; SymGetLineFromAddrW64( handle, symbol.Address, out int displacement, line); Match symbol_file_match = file_regex.Match(line.FileName); if (symbol_file_match.Success && $@"{symbol_file_match.Groups[1]}/{ symbol_file_match.Groups[2]}" == file && line.LineNumber < line_number) { start_line_number = line.LineNumber; } string url = Uri.EscapeUriString( $@"https://github.com/mockingbirdnest/Principia/blob/{commit}/{file}#{ (start_line_number.HasValue ? $"L{start_line_number}-" : "")}L{line_number}"); // Snippets should not be separated by new lines, as they are on their own // line anyway, so that a new line spaces them more than necessary. In // order to keep the Markdown readable, hide a new line in a comment. // `file:line` links need still to be separated by new lines. return(snippets ? $"<!---\n--> {url} " : $"\n[`{file}:{line_number}`]({url})"); }
private static void Main(string[] args) { bool unity_crash = false; Func <string, string> comment = Comment; bool snippets = true; string commit = null; if (args.Length < 2) { PrintUsage(); return; } string info_file_uri = args[0]; string principia_directory = args[1]; for (int i = 2; i < args.Length; ++i) { string flag = args[i]; var match = Regex.Match(flag, "--unity-crash-at-commit=([0-9a-f]{40})"); if (!unity_crash && match.Success) { unity_crash = true; commit = match.Groups[1].ToString(); } else if (snippets && flag == "--no-snippet") { snippets = false; } else if (comment == Comment && flag == "--no-comment") { comment = (_) => ""; } else { PrintUsage(); return; } } var web_client = new WebClient(); var stream = new StreamReader(web_client.OpenRead(info_file_uri), Encoding.UTF8); if (!unity_crash) { var version_regex = new Regex( @"^[EI].*\] Principia version " + @"([0-9]{10}-\w+)-[0-9]+-g([0-9a-f]{40})(-dirty)? built"); Match version_match; do { Check(!stream.EndOfStream, $"Could not find Principia version line in {info_file_uri}"); version_match = version_regex.Match(stream.ReadLine()); } while (!version_match.Success); commit = version_match.Groups[2].ToString(); bool dirty = version_match.Groups[3].Success; if (dirty) { Console.Write(comment( $"Warning: version is dirty; line numbers may be incorrect.")); } } Int64 principia_base_address = GetBaseAddress( unity_crash, @"GameData\\Principia\\x64\\principia.dll:principia.dll " + @"\(([0-9A-F]+)\)", "interface\\.cpp", stream); Console.Write( comment($"Using Principia base address {principia_base_address:X}")); var stack_regex = new Regex( unity_crash ? @"0x([0-9A-F]+) .*" : @"@\s+[0-9A-F]+\s+.* \[0x([0-9A-F]+)(\+[0-9]+)?\]"); Match stack_match; if (unity_crash) { Match stack_start_match; do { stack_start_match = Regex.Match(stream.ReadLine(), @"========== OUTPUTING STACK TRACE =================="); } while (!stack_start_match.Success); } do { stack_match = stack_regex.Match(stream.ReadLine()); } while (!stack_match.Success); IntPtr handle = new IntPtr(1729); SymSetOptions(SYMOPT_LOAD_LINES); Win32Check(SymInitializeW(handle, null, fInvadeProcess: false)); string principia_dll_path = Path.Combine(principia_directory, "principia.dll"); Win32Check(File.Exists(principia_dll_path)); Win32Check( SymLoadModuleExW(handle, IntPtr.Zero, principia_dll_path, null, principia_base_address, 0, IntPtr.Zero, 0) != 0); var trace = new Stack <string>(); for (; stack_match.Success; stack_match = stack_regex.Match(stream.ReadLine())) { Int64 address = Convert.ToInt64(stack_match.Groups[1].ToString(), 16); IMAGEHLP_LINEW64 line = new IMAGEHLP_LINEW64(); SYMBOL_INFOW symbol = new SYMBOL_INFOW(); int inline_trace = SymAddrIncludeInlineTrace(handle, address); if (inline_trace != 0) { Win32Check(SymQueryInlineTrace(handle, address, 0, address, address, out int current_context, out int current_frame_index)); for (int i = 0; i < inline_trace; ++i) { Win32Check(SymGetLineFromInlineContextW( handle, address, current_context + i, 0, out int dsp, line)); Win32Check(SymFromInlineContextW(handle, address, current_context + i, out Int64 displacement64, symbol)); trace.Push(ParseLine(handle, line, symbol, commit, snippets) ?? comment("Inline frame not in Principia code")); } } if (SymGetLineFromAddrW64(handle, address, out int displacement, line)) { Win32Check( SymFromAddrW(handle, address, out Int64 displacement64, symbol)); trace.Push(ParseLine(handle, line, symbol, commit, snippets) ?? comment($"Not in Principia code: {stack_match.Groups[0]}")); }
internal static extern bool SymGetLineFromAddrW64( IntPtr hProcess, Int64 dwAddr, out Int32 pdwDisplacement, [MarshalAs(UnmanagedType.LPStruct)] IMAGEHLP_LINEW64 Line);