static void Main(string[] args) { if (args.Length < 1) { Usage(); return; } int samples = 10; int sampleInterval = 1000; var state = ParseState.Unknown; foreach (var arg in args.Skip(1)) { switch (state) { case ParseState.Unknown: if (arg.ToLower() == "/s") { state = ParseState.Samples; } else if (arg.ToLower() == "/i") { state = ParseState.Interval; } else { Usage(); return; } break; case ParseState.Samples: if (!Int32.TryParse(arg, out samples)) { Usage(); return; } state = ParseState.Unknown; break; case ParseState.Interval: if (!Int32.TryParse(arg, out sampleInterval)) { Usage(); return; } state = ParseState.Unknown; break; default: break; } } string pidOrProcess = args[0]; var stats = new Dictionary<int, List<ThreadSnapshot>>(); var debugger = new MDbgEngine(); int pid = -1; var processes = Process.GetProcessesByName(pidOrProcess); if (processes.Length < 1) { try { pid = Int32.Parse(pidOrProcess); } catch { Console.WriteLine("Error: could not find any processes with that name or pid"); return; } } else { if (processes.Length > 1) { Console.WriteLine("Warning: multiple processes share that name, attaching to the first"); } pid = processes[0].Id; } MDbgProcess attached = null; try { attached = debugger.Attach(pid); } catch(Exception e) { Console.WriteLine("Error: failed to attach to process: " + e); return; } attached.Go().WaitOne(); for (int i = 0; i < samples; i++) { foreach (MDbgThread thread in attached.Threads) { var snapshot = ThreadSnapshot.GetThreadSnapshot(thread); List<ThreadSnapshot> snapshots; if (!stats.TryGetValue(snapshot.Id, out snapshots)) { snapshots = new List<ThreadSnapshot>(); stats[snapshot.Id] = snapshots; } snapshots.Add(snapshot); } attached.Go(); Thread.Sleep(sampleInterval); attached.AsyncStop().WaitOne(); } attached.Detach().WaitOne(); // perform basic analysis to see which are the top N stack traces observed, // weighted on cost Dictionary<Guid, long> costs = new Dictionary<Guid,long>(); Dictionary<Guid, string> stacks = new Dictionary<Guid, string>(); foreach (var stat in stats.Values) { long prevTime = -1; foreach (var snapshot in stat) { long time = snapshot.KernelTime + snapshot.UserTime; if (prevTime != -1) { foreach (var tuple in snapshot.StackHashes) { if (costs.ContainsKey(tuple.Item1)) { costs[tuple.Item1] += time - prevTime; } else { costs[tuple.Item1] = time - prevTime; stacks[tuple.Item1] = tuple.Item2; } } } prevTime = time; } } Console.WriteLine("Most expensive stacks"); Console.WriteLine("------------------------------------"); foreach (var group in costs.OrderByDescending(p => p.Value).GroupBy(p => p.Value)) { List<string> stacksToShow = new List<string>(); foreach (var pair in group.OrderByDescending(p => stacks[p.Key].Length)) { if (!stacksToShow.Any(s => s.Contains(stacks[pair.Key]))) { stacksToShow.Add(stacks[pair.Key]); } } foreach (var stack in stacksToShow) { Console.WriteLine(stack); Console.WriteLine("===> Cost ({0})", group.Key); Console.WriteLine(); } } var offenders = stats.Values .Select(_ => ThreadSnapshotStats.FromSnapshots(_)) .OrderBy(stat => stat.TotalKernelTime + stat.TotalUserTime) .Reverse(); foreach (var stat in offenders) { Console.WriteLine("------------------------------------"); Console.WriteLine(stat.ThreadId); Console.WriteLine("Kernel: {0} User: {1}", stat.TotalKernelTime, stat.TotalUserTime); foreach (var method in stat.CommonStack) { Console.WriteLine(method); } Console.WriteLine("Other Stacks:"); var prev = new List<string>(); foreach (var trace in stats[stat.ThreadId].Select(_ => _.StackTrace)) { if (!prev.SequenceEqual(trace)) { Console.WriteLine(); foreach (var method in trace) { Console.WriteLine(method); } } else { Console.WriteLine("<skipped>"); } prev = trace; } Console.WriteLine("------------------------------------"); } }
/// <summary>Starts the debugging process. This is synchronised on the Debugger /// object it is called on.</summary> /// <remarks> /// Assumes that the DebuggerSync object has been set up correctly and that something /// is listening to the BackgroundWorker events. It will lock indefinitely if something /// doesn't tell it to continue by calling set on the DebuggerSync.ContinueDebugExecution /// object. /// </remarks> public void Run() { lock (this) { int pid = DebugProcess.GetDebugProcessId(); CommandReceiver obj = DebugProcess.GetCommandReceiver(); // Get Assembly Search paths. These are used for finding // any assemblies that are used by the template. List<string> assemblySearchPaths = new List<string>(); foreach (string assemblyLocation in AssemblyLocations) { string dir = Path.GetDirectoryName(assemblyLocation).ToLower(); if (assemblySearchPaths.BinarySearch(dir) < 0) { assemblySearchPaths.Add(dir); assemblySearchPaths.Sort(); } } obj.ExecuteCommand(new RunFunctionCommand(CompiledAssemblyLocation, TestNamespace, TestClassname, TestFunctionName, ArgumentList, UserOptions, AaprojXml, IsFunctionStatic)); // This is a race condition, but it is required because we can't create the breakpoints // before attaching to the process. MDbgEngine debugger = new MDbgEngine(); proc = debugger.Attach(pid); _CurrentlyRunningBreakpoints.Clear(); foreach (int bp in _Breakpoints) { _CurrentlyRunningBreakpoints.Add(bp, proc.Breakpoints.CreateBreakpoint(CodeFilename, bp)); } proc.Breakpoints.CreateBreakpoint("Commands.cs", LAST_LINE_IN_MAIN); proc.Breakpoints.CreateBreakpoint(CodeFilename, LAST_LINE_IN_FUNCTION); _CurrentlyRunningProcess = proc; while (proc.IsAlive) { // Let the debuggee run and wait until it hits a debug event. switch (_DebuggerSync.NextDebugAction) { case DebugActionType.Continue: proc.Go().WaitOne(); break; case DebugActionType.StepInto: proc.StepInto(false).WaitOne(); break; case DebugActionType.StepOver: proc.StepOver(false).WaitOne(); break; case DebugActionType.StepOut: proc.StepOut().WaitOne(); break; case DebugActionType.Stop: _CurrentlyRunningProcess = null; proc.Breakpoints.DeleteAll(); proc.Detach(); break; } if (!proc.IsAlive) { proc = null; break; } // Get a DebugInformation object filled with the info we need. DebugInformation di = GetDebugInformation(proc); // If this is the last line, we need to stop debugging. if (di.StartLineNumber == LAST_LINE_IN_MAIN) { di.Stopped = false; di.StopReason = StopReason.DebuggerFinished; di.StopReasonText = "Debugger Finished"; _DebuggerSync.DebugBackgroundWorker.ReportProgress(99, di); _CurrentlyRunningProcess = null; proc.Breakpoints.DeleteAll(); proc.Detach(); break; } // Any other lines in the main function should just be skipped. if (di.SourceFile == "Commands.cs") { _DebuggerSync.NextDebugAction = DebugActionType.Continue; continue; } if (di.StartLineNumber == LAST_LINE_IN_FUNCTION) { _DebuggerSync.NextDebugAction = DebugActionType.StepOut; continue; } // If an exception was thrown, report it then stop debugging. if (di.ExceptionThrown) { di.Stopped = false; _DebuggerSync.DebugBackgroundWorker.ReportProgress(99, di); _CurrentlyRunningProcess = null; proc.Breakpoints.DeleteAll(); proc.Detach(); break; } di.Stopped = true; _DebuggerSync.DebugBackgroundWorker.ReportProgress(50, di); _DebuggerSync.ContinueDebugExecution.WaitOne(); continue; } } _CurrentlyRunningProcess = null; }
public static MDbgProcess AttachToProcess(int pid, MDbgEngine debugger) { debugger.Options.SymbolPath = ""; string ver = null; try { ver = CorDebugger.GetDebuggerVersionFromPid(pid); } catch (Exception ex) { //a problem getting the version doesn't mean that we can't attach, try with the default ver ver = CorDebugger.GetDefaultDebuggerVersion(); } MDbgProcess proc = debugger.Attach(pid, null, ver); DebuggerUtils.DrainAttach(debugger, proc); return proc; }
public static void AttachCmd(int pid, MDbgEngine debugger) { const string versionArg = "ver"; const string continuationEventArg = "attachEvent"; const string pidArg = "pid"; //ArgParser ap = new ArgParser(arguments, versionArg + ":1;" + continuationEventArg + ":1;" + pidArg + ":1"); //if (ap.Count > 1) //{ // throw new MDbgShellException("Wrong # of arguments."); //} //if (!ap.Exists(0) && !ap.OptionPassed(pidArg)) //{ // WriteOutput("Please choose some process to attach"); // ProcessEnumCmd(""); // return; //} //int pid; //if (ap.Exists(0)) //{ // pid = ap.AsInt(0); // if (ap.OptionPassed(pidArg)) // { // //WriteOutput("Do not specify pid option when also passing pid as last argument"); // return; // } //} //else //{ // Debug.Assert(ap.OptionPassed(pidArg)); // verified above // pid = ap.GetOption(pidArg).AsInt; //} // // Do some sanity checks to give useful end-user errors. // // Can't attach to ourselves! if (Process.GetCurrentProcess().Id == pid) { throw new Exception("Cannot attach to myself!"); } // Can't attach to a process that we're already debugging. // ICorDebug may enforce this, but the error may not be very descriptive for an end-user. // For example, ICD may propogate an error from the OS, and the OS may return // something like AccessDenied if another debugger is already attached. // This only checks for cases where this same instance of MDbg is already debugging // the process of interest. foreach (MDbgProcess procOther in debugger.Processes) { if (pid == procOther.CorProcess.Id) { throw new Exception("Can't attach to process " + pid + " because it's already being debugged"); } } // Get the OS handle if there was one ///SafeWin32Handle osEventHandle = null; ///if (ap.OptionPassed(continuationEventArg)) ///{ /// osEventHandle = new SafeWin32Handle(new IntPtr(ap.GetOption(continuationEventArg).AsHexOrDecInt)); ///} // determine the version to attach to string version = MdbgVersionPolicy.GetDefaultAttachVersion(pid); //} if (version == null) { throw new Exception("Can't determine what version of the CLR to attach to in process " + pid + ". Use -ver to specify a version"); } // attach MDbgProcess p; //p = debugger.Attach(pid, osEventHandle, version); p = debugger.Attach(pid, null, version); p.Go().WaitOne(); //if (osEventHandle != null) //{ // osEventHandle.Dispose(); //} }
private static Dictionary<int, List<ThreadSnapshot>> CollectStats(SamplingParams sampling) { var stats = new Dictionary<int, List<ThreadSnapshot>>(); var debugger = new MDbgEngine(); MDbgProcess attached = null; try { attached = debugger.Attach(sampling.Pid); } catch (Exception e) { Console.WriteLine("Error: failed to attach to process: " + e); return stats; } attached.Go().WaitOne(); for (int i = 0; i < sampling.Samples; i++) { foreach (MDbgThread thread in attached.Threads) { try { var snapshot = ThreadSnapshot.GetThreadSnapshot(thread); List<ThreadSnapshot> snapshots; if (!stats.TryGetValue(snapshot.Id, out snapshots)) { snapshots = new List<ThreadSnapshot>(); stats[snapshot.Id] = snapshots; } snapshots.Add(snapshot); } catch (Exception ex) { Console.WriteLine("Exception getting sample #{0} from PID {1} : {2}", i, sampling.Pid, ex.ToString()); } } attached.Go(); Thread.Sleep(sampling.SampleInterval); attached.AsyncStop().WaitOne(); } attached.Detach().WaitOne(); return stats; }