65.9K
CodeProject 正在变化。 阅读更多。
Home

一个启动挂起进程的微型应用程序

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.95/5 (9投票s)

2011年7月22日

CPOL

2分钟阅读

viewsIcon

70581

downloadIcon

4018

一个小应用程序,用于启动一个挂起的进程,以便例如附加一个远程调试器。

引言

在本文中,我展示了一个简单的 WinForms 应用程序,它通过拖放启动一个任意的 .exe 文件,但将进程保持挂起状态。这样,就可以将(远程)调试器附加到该进程并设置启动函数的断点。

背景

有时,远程调试是查找应用程序中错误的最后手段。在我们的特定情况下,崩溃(嗯,它不是硬崩溃,否则我们可以生成一个迷你转储)会在启动后几秒钟内使应用程序关闭。

在这种情况下,使用 msvsmon 进行远程调试无法直接实现,因为 msvsmon 只能附加到正在运行的进程,而不能在远程机器上启动新进程。

Using the Code

只需启动应用程序,然后将 .exe 文件拖放到出现的表单上即可。

screenshot.png

您的应用程序将启动,保持运行几毫秒(通过跟踪栏确定),然后再次挂起。现在,您可以将调试器附加到该进程并在适当的位置(例如,在 InitInstance() 中,如果您使用的是 MFC)设置断点。

started_dialog.png

当您单击消息框时,应用程序将继续运行,并最终到达您的断点。

代码工作原理

启动应用程序

从 .NET 无法直接在挂起模式下启动应用程序;相反,需要 P/Invoke。特别是,我们需要创建挂起进程并随后再次恢复进程的方法。此外,稍后还需要一种再次挂起主线程的方法(有关详细信息,请参阅 blogs.msdn 上的这篇文章)。

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);
}

参数 STARTUPINFOPROCESS_INFORMATION 分别对应于 .NET 中的 ProcessStartInfoProcess 类,位于 Systems.Diagnostics 命名空间中。

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;
}

与调用 Process.Start() 的主要区别在于能够向方法 CreateProcess 提供 ProcessCreationFlags

[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
}

使用这些方法和结构,我们现在可以启动给定的应用程序。重要的一点是最后一行中的标志 ProcessCreationFlags.CREATE_SUSPENDED

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

refout 参数 sipi 包含有关创建的进程的重要信息,特别是主线程的句柄 (pi.hThread) 和进程的 PID。

恢复和挂起应用程序

要最终启动应用程序,必须恢复主线程

IntPtr ThreadHandle = pi.hThread;
NativeMethods.ResumeThread(ThreadHandle);

要再次挂起它,可以调用方法 SuspendThread

NativeMethods.SuspendThread(ThreadHandle);

特别是,我将应用程序恢复几毫秒(只需阻塞等待)并再次挂起线程。然后,我显示消息框。这样,应用程序已经启动足够长的时间,以便调试器可以附加。

如果我跳过这个初始延迟,我将在调试器中附加时遇到崩溃

Debugger:: An unhandled non-continuable exception was thrown during process load

关注点

该程序仅启动应用程序 (.exe),不会启动链接。

历史

  • 2011 年 7 月 22 日:初始帖子。
  • 2011 年 7 月 23 日:添加了一些关于启动、恢复和挂起代码的描述。
© . All rights reserved.