private void RestartWithx64() { if (Environment.Is64BitProcess) { return; } if (IsChild) { Console.WriteLine("Recursion detected. Do not start any further child process. Please file an issue at https://github.com/Alois-xx/MemAnalyzer/issues"); return; } // If csv output file was already opened close it to allow child process to write to it. if (OutputStringWriter.CsvOutput == true) { OutputStringWriter.Output.Close(); OutputStringWriter.Output = Console.Out; OutputStringWriter.CsvOutput = false; } string exe = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "MemAnalyzerx64.exe"); // if -debug is specified do not deploy the exe into AppData. Instead start it from the location where where the main exe is located if (!IsDebug) { string appDir = GetToolDeployDirectory(); Directory.CreateDirectory(appDir); exe = Path.Combine(appDir, "MemAnalyzerx64.exe"); } if (!File.Exists(exe)) { File.WriteAllBytes(exe, Binaries.MemAnalyzerx64); // Deploy app.config File.WriteAllText(exe + ".config", Binaries.App); } DebugPrinter.Write("Starting x64 child {0}", exe); ProcessStartInfo info = new ProcessStartInfo(exe, GetQuotedArgs() + " -child") { UseShellExecute = false, WindowStyle = ProcessWindowStyle.Hidden, CreateNoWindow = true, RedirectStandardOutput = true }; var p = Process.Start(info); Console.CancelKeyPress += (a, b) => p.Kill(); string output = null; while ((output = p.StandardOutput.ReadLine()) != null) { Console.WriteLine(output); } p.WaitForExit(); DebugPrinter.Write($"Setting return code from x64 process {p.ExitCode} to our own return code."); ReturnCode = p.ExitCode; }
public ClrHeap GetHeap(DataTarget target) { var clrVersion = target.ClrVersions.FirstOrDefault(); if (clrVersion == null) { return(null); } ClrRuntime runtime = null; try { runtime = clrVersion.CreateRuntime(); return(runtime.GetHeap()); } catch (Exception ex) { DebugPrinter.Write("Got from CreateRuntime or GetHeap: {0}", ex); // If it is a target architecture mismatch hide error message and start x64 to try again later. if (Environment.Is64BitProcess) { Console.WriteLine("Error: Is the dump file opened by another process (debugger)? If yes close the debugger first."); Console.WriteLine(" If the dump comes from a different computer with another CLR version {0} that you are running on your machine you need to download the matching mscordacwks.dll first. Check out " + DacCollection + " and download the matching version/s.", clrVersion.Version); Console.WriteLine(" Then set _NT_SYMBOL_PATH=PathToYourDownloadedMscordackwks.dll e.g. _NT_SYMBOL_PATH=c:\\temp\\mscordacwks in the shell where you did execute MemAnalyzer and then try again."); Console.WriteLine(); } throw; } }
/// <summary> /// Query can be 100;N#1 or N#1 or 100 /// </summary> /// <param name="query">Query string to parse</param> private void ParseTopNQuery(string query, out int n, out int minCount) { var parts = query.Replace(" ", "").Split(";".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); string condition = null; minCount = 0; if (int.TryParse(parts[0], out n) == false) { condition = parts[0]; } else if (parts.Length > 1) { condition = parts[1]; } if (condition != null) { // cannot use > sign because the shell will interpret it was file redirection character. if (condition.IndexOf("N", StringComparison.OrdinalIgnoreCase) == 0 && condition.IndexOf('#') == 1) { int.TryParse(condition.Substring(2), out minCount); DebugPrinter.Write($"Condition: {condition}, MinValue: {minCount}"); } else { Console.WriteLine($"Warning condition {condition} of query {query} could not be parsed."); Console.WriteLine("A query contains ddd;N#ddd2 where ddd is the TopN limit and dddd2 will print only types which have an instance count > ddd2"); } } DebugPrinter.Write($"N: {n}, MinCount: {minCount}"); }
/// <summary> /// Save VMMap data to output file /// </summary> /// <param name="pid"></param> /// <param name="outFile"></param> public static void SaveVMmapDataToFile(int pid, string outFile) { var info = new ProcessStartInfo(VmMap, $"-accepteula -p {pid} {outFile}") { CreateNoWindow = true, WindowStyle = ProcessWindowStyle.Hidden, }; try { var p = Process.Start(info); p.WaitForExit(); // Looks like a bug in VMMap where the x64 child process is still writing the output data but the // x86 parent process has already exited. for (int i = 0; i < 10 && !File.Exists(outFile); i++) { DebugPrinter.Write($"VMMap has exited but output file {outFile} does not yet exist"); Thread.Sleep(2000); } } catch (Exception ex) { NoVMMap = true; // do not try again if not present. DebugPrinter.Write($"Could not start VMMap: {info.FileName} {info.Arguments}. Got: {ex}"); } }
/// <summary> /// Fixes issues with tools deployed side by side with the executable but the current working directory might /// be different. /// </summary> private static void AddProcessStartDirectoryToPath() { string path = Environment.GetEnvironmentVariable("PATH"); path += ";" + Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); Environment.SetEnvironmentVariable("PATH", path); DebugPrinter.Write($"Set Path={path}"); }
private bool CanLoadDump(string dumpFileName) { ProcessStartInfo info = new ProcessStartInfo(Assembly.GetEntryAssembly().Location, $"-f {dumpFileName} -vmmap") { UseShellExecute = false, RedirectStandardOutput = true, CreateNoWindow = true, }; var p = Process.Start(info); Console.WriteLine("Verifying Dump if it is readable ..."); Console.WriteLine(p.StandardOutput.ReadToEnd()); DebugPrinter.Write($"Start child process {info.FileName} with args: {info.Arguments}"); p.WaitForExit(); return(p.ExitCode > 0); }
static int Main(string[] args) { try { AppDomainResolverFromResources resolver = new AppDomainResolverFromResources(typeof(Binaries)); var p = new Program(args); if (p.Parse()) { p.Run(); } } catch (Exception ex) { ReturnCode = -1; Help("Got Exception: {0}", ex); } DebugPrinter.Write($"Returning value {ReturnCode} as exit code."); return(ReturnCode); }
MemAnalyzerBase CreateAnalyzer(Actions action) { ClrHeap heap = null; ClrHeap heap2 = null; // If only one dump file is opened then we can open the file with the debugger interface which only supports // one reader in one process. This is useful because otherwise we will lock the file and ClrMD will error // out if the dump file already open within a debugger which is pretty common. // But the drawback is that if we compare a live process against a memory dump it will fail and we get // always a diff of 0 back! var crashDumpOpenMode = TargetInformation.IsProcessCompare ? CrashDumpReader.ClrMD : CrashDumpReader.DbgEng; try { if (TargetInformation.DumpFileName1 != null) { Target = DataTarget.LoadCrashDump(TargetInformation.DumpFileName1, crashDumpOpenMode); heap = GetHeap(Target); } if (TargetInformation.DumpFileName2 != null) { Target2 = DataTarget.LoadCrashDump(TargetInformation.DumpFileName2, crashDumpOpenMode); heap2 = GetHeap(Target2); } if (TargetInformation.Pid1 != 0 && Target == null) { if (!NativeMethods.ProcessExists(TargetInformation.Pid1)) { Console.WriteLine($"Error: Process {TargetInformation.Pid1} is not running."); return(null); } // do not try wrong bitness when we know it will fail anyway if (NativeMethods.IsWin64(TargetInformation.Pid1) && !Environment.Is64BitProcess) { DebugPrinter.Write($"Starting x64 because Pid {TargetInformation.Pid1} is Win64 process"); RestartWithx64(); return(null); } Target = DataTarget.AttachToProcess(TargetInformation.Pid1, 5000u); heap = GetHeap(Target); if (heap == null) { Console.WriteLine($"Error: Could not get managed heap of process {TargetInformation.Pid1}. Most probably it is an unmanaged process."); } } if (TargetInformation.Pid2 != 0 & Target2 == null) { if (!NativeMethods.ProcessExists(TargetInformation.Pid2)) { Console.WriteLine($"Error: Process {TargetInformation.Pid2} is not running."); return(null); } // Cannot load data from processes with different bitness if (TargetInformation.Pid1 != 0 && (NativeMethods.IsWin64(TargetInformation.Pid1) != NativeMethods.IsWin64(TargetInformation.Pid2))) { Console.WriteLine($"Error: Process {TargetInformation.Pid1} and {TargetInformation.Pid2} are of different bitness. You can dump each one separately to CSV files and compare the CSV files instead."); return(null); } // do not try wrong bitness when we know it will fail anyway if (NativeMethods.IsWin64(TargetInformation.Pid2) && !Environment.Is64BitProcess) { DebugPrinter.Write($"Starting x64 because Pid2 {TargetInformation.Pid2} is Win64 process"); RestartWithx64(); return(null); } Target2 = DataTarget.AttachToProcess(TargetInformation.Pid2, 5000u); heap2 = GetHeap(Target2); } } catch (Exception ex) { // Default is a 32 bit process if runtime creation fails with InvalidOperationException or a DAC location failure // we try x64 as fall back. This will work if the bitness of the dump file is wrong. if (!Environment.Is64BitProcess && (ex is FileNotFoundException || ex is InvalidOperationException || ex is ClrDiagnosticsException || (ex is COMException && ex.HResult == HResultNotEnoughStorage))) { RestartWithx64(); } else { if (ex is Win32Exception win32 && win32.NativeErrorCode == 5) { Console.WriteLine("Error: You need to run with elevated rights to access this process."); }