Introduction
New: appCompanion now has a codeplex page (contirbutions are welcome).
This small mdbg based program helps you get some inside information about the working of your program in a client's (non dev) environment,
without installing VisualStudio or learning how to use windbg.
Using this you can:
- Get an application callstack from all threads, on the fly.
- Take a small performance snapshot, showing you where the time is beeing wasted.
- View the system's debug console (dbgView)
- Listen to exceptions (first + second time)
- Take hang dumps for the process.
This tool can help in more ways than are initialy visible, for example getting callstacks can help in spotting where in the code an unknown flow runs,
by opening a dialog and finding out its name in the callstack, or by diagnosing a hanged process as running with a hidden dialog behind the main app.
[Download demo project] [Download source]
In the making of this program I also used Chritian-Birkl's excellent
CodeProject article about replicating DebugView's abilities with C#.
32/64 bit support
the first versio of this article was compiled as x86, which limited it's scope. This is now fixed:
- Since the debugger is platform specific, you can't use a x64 debugger to debug a x86 process and vice versa.
- AnyCPU will be run as x64 on a x64 OS and as x86 on a x86 OS.
- To use with AnyCPU/x64 apps, you need to compile AppCompanion using AnyCPU.
- For x86 applications compile AppCompanion using x86 in the exe project properties.
- The demo zip now includes both versions.
- When trying to attach to a process with an incorrect platform, the AppCompanion will show an error baloon and exit.
Background
After several years of using windbg+sos to get debug info from code running in preprod and production environments, I discovered MSE,
only to lose it again when .NET 4.0 came and it was obsolete. (today there are several alternatives)
Lately i have been thinking that i can use mdbg to create
something that can help me track my app, which can give a bit more than MSE (though less than windbg), in an elegant way.
I tried to gather some helpful functions for Dev (and QA personnel as well) to use for getting more data when the application isn't providing enough.
Using the AppCompanion demo
Edit the AppCompanion\AppStateMonitor.exe.config file, change myProgram to your program name, without the ".exe" extention.
- when running the AppCompanion, it will create a small tray icon (you might need to customize windows tray to show it)
- when it identifies the target application, it will "steal" it's icon and use it as it's own.
- A right click on this icon gives you all the options described in the first paragraph of this article
About the source
Making mdbg work
I used Microsoft's mdbg 4.0 sample, to attach and get info from the process. Since the debugger requires a MTA thread, and graphics require STA threads.
To go around this conflict, I used TPL Tasks to run the debugger code, and since TPL threads (and basically all managed threads) are MTA by default, this did the trick.
I'm still toying around with the idea of putting the debugger code in a single thread with a custom message pump using a blocking collection, but can't find a really good reason to...
Task t = Task.Factory.StartNew(() =>
{
MDbgEngine debugger = new MDbgEngine();
MDbgProcess process = DebuggerUtils.AttachToProcess(m_processId, debugger);
process.CorProcess.Stop(0);
process.Detach();
});
return t;
Task t = Task.Factory.StartNew(() =>
{
if (m_process.IsAlive)
{
m_process.CorProcess.Stop(0);
m_process.Detach();
}
});
t.Wait();
Taking performace samples
The sampling/performace code, works the same way some profilers work when sampling:
- Take a snapshot every x miliseconds
- Merge the samples to a call tree
- Increment each node according to the number of times this call was seen.
This gives you a good sense of how much time was spent on each node, and can easily be multiplied by the number of miliseconds between samples to get a good idea of the actual time.
Debugger console
I added this since I found it usefull for my own purposes, the code isn't mine but Chritian-Birkl's
as stated above.
Taking hang/crash dumps
Dumps are taken using a pinvok to MiniDumpWriteDump
function in the dbghelp.dll:
[DllImport("dbghelp.dll", EntryPoint = "MiniDumpWriteDump",
CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
static extern bool MiniDumpWriteDump(IntPtr hProcess, uint processId, SafeHandle hFile,
uint dumpType, IntPtr expParam, IntPtr userStreamParam, IntPtr callbackParam);
[DllImport("kernel32.dll", EntryPoint = "GetCurrentThreadId", ExactSpelling = true)]
static extern uint GetCurrentThreadId();
public static bool WriteDump(int pid, int threadWithExceptionId,
string filename, DumpOptions options, bool hasException)
{
using (FileStream dumpFile = File.OpenWrite(filename))
{
SafeHandle fileHandle = dumpFile.SafeFileHandle;
Process currentProcess = Process.GetProcessById(pid);
IntPtr currentProcessHandle = currentProcess.Handle;
uint currentProcessId = (uint)currentProcess.Id;
MiniDumpExceptionInformation exp;
exp.ThreadId = threadWithExceptionId; exp.ClientPointers = false;
exp.ExceptionPointers = IntPtr.Zero;
if (hasException)
{
exp.ExceptionPointers = System.Runtime.InteropServices.Marshal.GetExceptionPointers();
}
bool bRet = false;
if (exp.ExceptionPointers == IntPtr.Zero)
{
bRet = MiniDumpWriteDump(currentProcessHandle, currentProcessId,
fileHandle, (uint)options, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
}
else
{
bRet = MiniDumpWriteDump(currentProcessHandle, currentProcessId,
fileHandle, (uint)options, ref exp, IntPtr.Zero, IntPtr.Zero);
}
return bRet;
}
}
- Dumps can be taken in any time, not depending on debugger activity.
- The application will snap a dump on uncaught exception while listening for exceptions and
ProcessListener.WriteDumpOnUncaughtExceptions
is set to true (false by default).
Potential future feaures and improvements
- Adding the ability to create breakpoints
- Code evaluation when stopped in breakpoints
- Sampling with a moving window/untill stopped instead of a constant time.
I hope you find this usefull as a tool or as a code sample for the use of mdbg.
I might consider making this an open source project if there is a strong demand, but for now it's just something i mocked up in my spare time. =)