准备调试服务应用程序

本主题列出了调试服务应用程序之前可能需要的所有准备步骤。 方案中需要哪些步骤取决于你选择的附加选项以及你选择的调试配置。 有关这些选项的列表,请参阅 选择调试服务应用程序的最佳方法

本主题中所述的每个准备步骤都指定了所需的条件。 这些步骤可以按任意顺序完成。

启用初始化代码的调试

如果计划从服务应用程序的执行开始就对其进行调试,包括其初始化代码,则需要执行此准备步骤。

找到或创建以下注册表项,其中 ProgramName 是服务应用程序的可执行文件的名称:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\ProgramName 

ProgramName 应包含文件扩展名,但不包括路径。 例如,ProgramName 可能是 Myservice.exe 或 Thisservice.dll。

在此注册表项下,创建名为 调试器的字符串数据值。 此字符串的值应设置为要附加到服务应用程序的调试器的完整路径和文件名。

  • 如果打算在本地调试,请使用如下所示的字符串:

    c:\Debuggers\windbg.exe 
    

    如果你运行的是 Windows Vista 或更高版本的 Windows,请不要选择此选项。

  • 如果计划使用远程调试,请使用 -noio 选项指定 NTSD。 这会导致 NTSD 在没有自己的任何控制台的情况下运行,只能通过远程连接进行访问。 例如:

    c:\Debuggers\ntsd.exe -server ServerTransport -noio -y SymbolPath 
    

    如果调试会话在 Windows 完全加载之前开始,则可能无法从远程共享访问符号;在这种情况下,必须使用本地符号。 ServerTransport 必须指定由 Windows 内核实现的传输协议,而无需与用户模式服务(如 TCP 或 NPIPE)交互。 有关 ServerTransport的语法,请参阅 激活调试服务器

  • 如果您计划通过内核模式调试器来控制用户模式调试器,请指定带有 -d 选项的 NTSD。 例如:

    c:\Debuggers\ntsd.exe -d -y SymbolPath 
    

    如果计划使用此方法,并且将从符号服务器访问用户模式符号,则应将此方法与远程调试相结合。 在这种情况下,请使用 -ddefer 选项指定 NTSD。 选择由 Windows 内核实现的传输协议,而不与用户模式服务(如 TCP 或 NPIPE)交互。 例如:

    c:\Debuggers\ntsd.exe -server ServerTransport -ddefer -y SymbolPath 
    

    有关详细信息,请参阅从内核调试器控制用户模式调试器

完成此注册表编辑后,每当启动或重新启动具有此名称的服务时,将启动调试器。

使服务应用程序能够进入调试器

如果希望服务应用程序在发生故障或遇到异常时闯入调试器,则需要执行此准备步骤。 如果希望服务应用程序通过调用 DebugBreak 函数来中断调试器,则还需要执行此步骤。

注意 如果已启用初始化代码调试(“启用初始化代码调试”子节中所述的步骤),则应跳过此步骤。 启用初始化代码调试后,调试器会在服务应用程序启动时自动附加,导致所有崩溃、异常和对 DebugBreak 的调用都会被直接路由到调试器,无需额外准备。

此准备步骤涉及将所选调试器注册为事后调试器。 这是通过使用调试器命令行上的 -iae 或 -iaec 选项来完成的。 我们建议使用以下命令,但如果想要更改它们,请参阅 启用事后调试中的语法详细信息。

  • 如果打算在本地调试,请使用如下命令:

    windbg -iae 
    

    如果你运行的是 Windows Vista 或更高版本的 Windows,请不要选择此选项。

  • 如果计划使用远程调试,请使用 -noio 选项指定 NTSD。 这会导致 NTSD 在没有自己的任何控制台的情况下运行,只能通过远程连接进行访问。 若要安装包含 -server 参数的事后调试器,必须手动编辑注册表;有关详细信息,请参阅 启用事后调试。 例如,AeDebug 键的 Debugger 值可能如下:

    ntsd -server npipe:pipe=myproc%x -noio -p %ld -e %ld -g -y SymbolPath 
    

    在管道规范中,%x 令牌被替换为启动调试器的进程的进程 ID。 这可以保证,如果多个进程启动事后调试器,则每个进程都有唯一的管道名称。 如果调试会话在 Windows 完全加载之前开始,则可能无法从远程共享访问符号;在这种情况下,必须使用本地符号。 ServerTransport 必须指定由 Windows 内核实现的传输协议,而无需与用户模式服务(如 TCP 或 NPIPE)交互。 有关 ServerTransport的语法,请参阅 激活调试服务器

  • 如果计划从内核模式调试器控制用户模式调试器,请使用 -d 选项指定 NTSD。 例如:

    ntsd -iaec -d -y SymbolPath 
    

    如果选择此方法并打算从符号服务器访问用户模式符号,则应将此方法与远程调试相结合。 在这种情况下,请使用 -ddefer 选项指定 NTSD。 选择由 Windows 内核实现的传输协议,而不与用户模式服务(如 TCP 或 NPIPE)交互。 若要安装包含 -server 参数的事后调试器,必须手动编辑注册表;有关详细信息,请参阅 启用事后调试。 例如,AeDebug 键的 Debugger 值可能如下:

    ntsd -server npipe:pipe=myproc%x -ddefer -p %ld -e %ld -g -y SymbolPath 
    

    有关详细信息,请参阅从内核调试器控制用户模式调试器

发出其中一个命令时,会注册事后调查调试器。 每当任何用户模式程序(包括服务应用程序)遇到异常或运行 DebugBreak 函数时,都会启动此调试器。

调整服务应用程序超时

如果计划自动启动调试器(服务启动时或遇到异常时),则需要此准备步骤。

找到以下注册表项:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control

在此键下,找到或创建名为 ServicesPipeTimeout的 DWORD 数据值。 将此条目设置为希望服务在超时之前等待的时间(以毫秒为单位)。例如,值为 60,000 为 1 分钟,而值为 86,400,000 为 24 小时。 如果未设置此注册表值,则默认超时约为 30 秒。

此值的意义在于,当启动每个服务时,时钟开始运行,达到超时值时,附加到服务的任何调试器都将终止。 因此,所选的值应该比启动服务与调试会话完成之间的总时间长。

此设置适用于注册表编辑完成后启动或重启的每个服务。 如果某些服务崩溃或挂起,并且此设置仍然有效,则 Windows 不会检测到该问题。 因此,只有在调试完成后,才应使用此设置,并将注册表项返回到其原始值。

隔离服务

有时,多个服务在单个服务主机(Svchost)进程中合并。 如果要调试此类服务,必须先将其隔离到单独的 Svchost 进程中。

可通过三种方法隔离服务。 Microsoft 建议使用“将服务移动到自己的组”方法,如下所示。 替代方法(更改服务类型和复制 SvcHost 二进制文件)可以临时用于调试,但由于它们改变了服务运行的方式,因此它们不如第一种方法那么可靠。

首选方法:将服务移动到自己的组

  1. 发出以下服务配置工具(Sc.exe)命令,其中 ServiceName 是服务的名称:

    sc qc ServiceName 
    

    这将显示服务的当前配置值。 感兴趣的值是BINARY_PATH_NAME,它指定用于启动服务控制程序的命令行。 在这种情况下,由于服务尚未隔离,因此此命令行包含一个目录路径 Svchost.exe 和一些 SvcHost 参数,包括 -k 开关,后跟一个组名。 例如,它可能如下所示:

    %SystemRoot%\System32\svchost.exe -k LocalServiceNoNetwork 
    

    请记住此路径和组名称;它们用于步骤 5 和 6。

  2. 找到以下注册表项:

    HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsNT\CurrentVersion\SvcHost 
    

    创建具有唯一名称的新REG_MULTI_SZ值(例如,TempGrp)。

  3. 将此新值设置为等于要隔离的服务的名称。 不包括任何目录路径或文件扩展名。 例如,您可能将新值 TempGrp 设置为等于 MyService

  4. 在同一注册表项下,创建与步骤 2 中使用的同名的新密钥。 例如:

    HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsNT\CurrentVersion\SvcHost\TempGrp 
    

    现在,SvcHost 密钥包含具有新名称的值,并且还包含具有相同名称的从属键。

  5. 查找 SvcHost 密钥的另一个从属密钥,该密钥与你在步骤 1 中找到的组同名。 如果存在此类键,请检查其中的所有值,并在步骤 4 中创建的新密钥中创建这些值的副本。

    例如,旧密钥可能命名为:

    HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsNT\CurrentVersion\SvcHost\LocalServiceNoNetwork 
    

    可能包含 CoInitializeSecurityParamAuthenticationCapabilities 和其他值。 您将访问新创建的密钥。

    HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsNT\CurrentVersion\SvcHost\TempGrp 
    

    并在其中创建名称、类型和数据与旧键完全相同的值。

    如果旧密钥不存在,则无需创建新密钥。

  6. 使用以下服务配置工具命令修改步骤 1 中找到的路径:

    sc config ServiceName binPath= "RevisedPath" 
    

    在此命令中,ServiceName 是服务的名称,RevisedPath 是要为BINARY_PATH_NAME提供的新值。 对于 RevisedPath,请使用与步骤 1 中显示的路径完全相同的路径,包括该行上显示的所有选项,只做一个更改:将 -k 开关后面的参数替换为在步骤 2 中创建的新注册表值的名称。 将 RevisedPath 括在引号中。 等号后的空格是必需的。

    例如,命令可能如下所示:

    sc config MyService binPath= "%SystemRoot%\System32\svchost.exe -k TempGrp" 
    

    可能需要再次使用 sc qc 命令来查看所做的更改。

这些设置将在下次启动服务时生效。 若要清除旧服务的影响,建议重启 Windows,而不仅仅是重启服务。

完成调试后,如果要将此服务返回到共享服务主机,请再次使用 sc config 命令将二进制路径返回到其原始值,并删除创建的新注册表项和值。

替代方法:更改服务类型

  1. 发出以下服务配置工具(Sc.exe)命令,其中 ServiceName 是服务的名称:

    sc config ServiceName type= own 
    

    等号后必须有空格。

  2. 使用以下命令重启服务:

    net stop ServiceName 
    net start ServiceName 
    

此替代方法不是建议的方法,因为它可以更改服务的行为。 如果确实使用此方法,请在完成调试后,使用以下命令还原为正常行为:

sc config ServiceName type= share 

替代方法:复制 SvcHost 二进制

  1. Svchost.exe 可执行文件位于 Windows 的 system32 目录中。 创建此文件的副本,将其命名为 svhost2.exe,并将其放置在 system32 目录中。

  2. 发出以下服务配置工具(Sc.exe)命令,其中 ServiceName 是服务的名称:

    sc qc ServiceName 
    

    此命令显示服务的当前配置值。 感兴趣的值是BINARY_PATH_NAME,它指定用于启动服务控制程序的命令行。 在此方案中,由于服务尚未隔离,因此此命令行将包含目录路径、Svchost.exe,以及一些 SvcHost 参数。 例如,它可能如下所示:

    %SystemRoot%\System32\svchost.exe -k LocalServiceNoNetwork 
    
  3. 若要修改此路径,请发出以下命令:

    sc config ServiceName binPath= "RevisedPath" 
    

    在此命令中,ServiceName 是服务的名称,RevisedPath 是要为BINARY_PATH_NAME提供的新值。 对于 RevisedPath,请使用与步骤 2 中显示的路径完全相同的路径,包括该行上显示的所有选项,只进行更改:将 Svchost.exe 替换为 Svchost2.exe。 将 RevisedPath 括在引号中。 等号后面需要有空格。

    例如,命令可能如下所示:

    sc config MyService binPath= "%SystemRoot%\System32\svchost2.exe -k LocalServiceNoNetwork" 
    

    可以再次使用 sc qc 命令查看所做的更改。

  4. 使用以下命令重启服务:

    net stop ServiceName 
    net start ServiceName 
    

此替代方法不是建议的方法,因为它可以更改服务的行为。 如果使用此方法,请使用 sc config 命令在完成调试后将路径更改回其原始值。