Introduction
In this article, I present a simple WinForms-application that starts an arbitrary .exe via drag and drop but leaves the process suspended. This way, it is possible to attach a (remote) debugger to the process and set breakpoints in startup functions.
Background
Sometimes, remote debugging is the last resort for finding a bug in an application. In our particular case, the crash (well, it was no hard crash, otherwise we could have generated a mini-dump) would make the application shut down just a few seconds after startup.
In such a case, remote debugging with msvsmon is not directly possible, since msvsmon can only attach to running processes but not launch a new process on the remote machine.
Using the Code
Just start the application and drop the .exe onto the appearing form.
Your application will be started, stay alive for a few milliseconds (determined via the trackbar), and is then suspended again. Now you can attach the debugger to the process and set breakpoints at the appropriate places (e.g., in InitInstance()
if you are using MFC).
As soon as you click the message box, the application continues and will eventually reach your breakpoint.
How the Code Works
Launching an Application
It is not directly possible from .NET to launch an application in suspended mode; instead, P/Invoke is necessary. In particular, we need methods to create the process suspended and to afterwards resume it again. Also, a method to suspend the main thread again will be needed later (see this post at blogs.msdn for details).
public static class NativeMethods
{
[DllImport("kernel32.dll")]
public static extern bool CreateProcess(string lpApplicationName,
string lpCommandLine, IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes,
bool bInheritHandles, ProcessCreationFlags dwCreationFlags,
IntPtr lpEnvironment, string lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[DllImport("kernel32.dll")]
public static extern uint ResumeThread(IntPtr hThread);
[DllImport("kernel32.dll")]
public static extern uint SuspendThread(IntPtr hThread);
}
The parameters STARTUPINFO
and PROCESS_INFORMATION
correspond to the .NET ProcessStartInfo
and Process
classes in the Systems.Diagnostics
namespace, respectively.
public struct STARTUPINFO
{
public uint cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public uint dwX;
public uint dwY;
public uint dwXSize;
public uint dwYSize;
public uint dwXCountChars;
public uint dwYCountChars;
public uint dwFillAttribute;
public uint dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
public struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public uint dwProcessId;
public uint dwThreadId;
}
The essential difference to invoking Process.Start()
is the possibility to supply ProcessCreationFlags
to the method CreateProcess
:
[Flags]
public enum ProcessCreationFlags : uint
{
ZERO_FLAG = 0x00000000,
CREATE_BREAKAWAY_FROM_JOB = 0x01000000,
CREATE_DEFAULT_ERROR_MODE = 0x04000000,
CREATE_NEW_CONSOLE = 0x00000010,
CREATE_NEW_PROCESS_GROUP = 0x00000200,
CREATE_NO_WINDOW = 0x08000000,
CREATE_PROTECTED_PROCESS = 0x00040000,
CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000,
CREATE_SEPARATE_WOW_VDM = 0x00001000,
CREATE_SHARED_WOW_VDM = 0x00001000,
CREATE_SUSPENDED = 0x00000004,
CREATE_UNICODE_ENVIRONMENT = 0x00000400,
DEBUG_ONLY_THIS_PROCESS = 0x00000002,
DEBUG_PROCESS = 0x00000001,
DETACHED_PROCESS = 0x00000008,
EXTENDED_STARTUPINFO_PRESENT = 0x00080000,
INHERIT_PARENT_AFFINITY = 0x00010000
}
Using these methods and structures, we can now start the given application. The important point is the flag ProcessCreationFlags.CREATE_SUSPENDED
in the last but first line:
STARTUPINFO si = new STARTUPINFO();
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
bool success = NativeMethods.CreateProcess(processpath, null,
IntPtr.Zero, IntPtr.Zero, false,
ProcessCreationFlags.CREATE_SUSPENDED,
IntPtr.Zero, null, ref si, out pi);
The ref
and out
parameters si
and pi
contain important information about the created process, in particular a handle to the main thread (pi.hThread
) and the PID of the process.
Resuming and Suspending the Application
To finally start the application, the main thread must be resumed:
IntPtr ThreadHandle = pi.hThread;
NativeMethods.ResumeThread(ThreadHandle);
For suspending it again, the method SuspendThread
can be called:
NativeMethods.SuspendThread(ThreadHandle);
In particular, I resume the application for a few milliseconds (with just a blocking wait) and suspend the thread again. Afterwards, I show the message box. This way, the application has booted far enough for the debugger to attach.
If I skip this initial delay, I would get a crash in the debugger on attaching:
Debugger:: An unhandled non-continuable exception was thrown during process load
Points of Interest
The program will only start applications (.exe), it does not launch links.
History
- July 22, 2011: Initial post.
- July 23, 2011: Added some description of the code for launching, resuming, and suspending.