Click here to Skip to main content
Click here to Skip to main content

A tiny application to launch a suspended Process

By , 24 Jul 2011
 

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.

screenshot.png

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).

started_dialog.png

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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

hofingerandi
Software Developer
Austria Austria
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionAnother technique for debugging .NET applications on startupmemberJordanDay25 Jul '11 - 5:33 
Your utility is helpful for debugging non-.NET apps on startup, as well as on remote machines that don't have Visual Studio installed.
 
However, in the case where you're trying to debug a .NET app on startup and have VS on that machine, there is a (imo) simpler way:
 
Add a registry key to HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\your_application.exe
 
Add a string value named "debugger" with data set to "vsjitdebugger.exe".
 
Now, every time your_application.exe launches, the debugger will launch, allowing you to debug the application from the very start. Of course, once you've fixed the bug, you'll want to delete or rename this key, since having the debugger pop-up every time you run your_application.exe could be rather annoying.
QuestionRe: Another technique for debugging .NET applications on startupmemberhofingerandi25 Jul '11 - 10:16 
I am not sure if I understood that correctly:
If I already have Visual Studio installed, why would I not directly launch the application from there?
AnswerRe: Another technique for debugging .NET applications on startupmemberJordanDay26 Jul '11 - 4:49 
One area where I've found this personally useful is in debugging problems that show up in my applications after they're installed. Some problems do not manifest themselves in your built-from-source/debug versions -- only in the installed version. In this case, you can't just open up the source and debug -- you want to debug the installed version.
 
Additionally, say you don't even have the source, but do have the .pdb's -- being able to launch the application with the debugger attached can give you a great many more details than just examining the output of the windows crash report dialog.
GeneralRe: Another technique for debugging .NET applications on startupmemberhofingerandi27 Jul '11 - 10:21 
Now I understand.
 
One additional scenario that came to my mind is, when the application is not launched manually, but via some other process that is not directly under your control.
 
Btw, I just tried it with a native application, setting the registry value also works there!

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130516.1 | Last Updated 25 Jul 2011
Article Copyright 2011 by hofingerandi
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid