首页 文章

你如何调试Windows服务?

提问于
浏览
46

我阅读了有关该主题的MSDN文章 . 报价:

由于服务必须在服务控制管理器的上下文中而不是在Visual Studio中运行,因此调试服务并不像调试其他Visual Studio应用程序类型那样简单 . 要调试服务,必须启动该服务,然后将调试器附加到运行它的进程 . 然后,您可以使用Visual Studio的所有标准调试功能来调试应用程序 .

现在我的问题是我的服务首先无法启动 . 首先它崩溃,并说:

MyServiceName.exe [3596]中发生未处理的异常(System.Runtime.InteropServices.COMException)

并建议我调试它(当我选择一个时,调试器实例会立即崩溃) . 然后它说

无法在本地计算机上启动MyServiceName服务 . 错误1053:服务未及时响应启动或控制请求

那么,我如何调查/调试我的服务无法启动的原因?问题是我创建了一个控制台应用程序,它完全可以完成服务的工作,并且工作正常 . (我的意思是我只是将 OnStart ()方法's and the main loop'的内容复制到main) .

任何帮助,将不胜感激 .

该服务是用C#编写的,大量使用互操作 . 我正在使用VS2008

15 回答

  • 3

    您可以使用参数让您的应用程序决定是作为服务还是常规应用程序启动(即在本例中显示表单或启动服务):

    static void Main(string[] args)
    {
        if ((1 == args.Length) && ("-runAsApp" == args[0]))
        {
            Application.Run(new application_form());
        }
        else
        {
            System.ServiceProcess.ServiceBase[] ServicesToRun;
            ServicesToRun = new ServiceBase[] { new MyService() };
            System.ServiceProcess.ServiceBase.Run(ServicesToRun);
        }
    }
    

    现在,如果您传递参数"-runAsApp",您可以正常调试应用程序 - SCM将不会传递此参数,因此您也可以将其用作任何代码更改的服务(假设您派生自 ServiceBase

    Edit:

    与Windows服务的另一个区别是身份(这可能对InterOp尤为重要) - 您希望确保在“app”模式和服务模式下以相同的身份进行测试 .

    为此,您可以在应用模式下使用模拟(我可以发布C#包装器,如果它有帮助,但可以轻松搜索),以使用您的Windows服务将运行的相同标识,即通常是LocalService或NetworkService .

    如果需要其他身份,您可以向app.config添加设置,以便您决定是否使用凭据,如果是,则使用哪个用户进行模拟 - 这些设置在作为应用程序运行时处于活动状态,但是对于Windows服务已关闭(因为服务已经在所需的身份下运行):

    <appSettings>
        <add key="useCredentials" value="false"/>
        <add key="user" value="Foo"/>
        <add key="password" value="Bar"/>
      </appSettings>
    
  • 1

    我通常只是手动设置断点,然后将其指向c#中当前打开的项目 . 设置断点的代码是:

    System.Diagnostics.Debugger.Break();
    

    这应该让你开始,然后你可以单步执行代码,看看到底发生了什么 .

  • 7

    我从C. Lawrence Wenham那里偷了这个,所以我不能真正理解,但你可以通过以下代码以编程方式将调试器附加到服务,而不会在此时断开执行:

    System.Diagnostics.Debugger.Launch();
    

    将它放在服务的OnStart()方法中,作为第一行,它将提示您选择VS的实例来附加其调试器 . 从那里,系统将在您设置的断点处停止,并在抛出异常时停止 . 我会在代码周围放置一个 #if DEBUG 子句,因此Release版本不会包含它;或者你可以在发现问题后将其剥离 .

  • 2

    您可以使用WinDbg / NTSD(“Windows调试工具”软件包中的另一个调试器)与您的服务一起启动调试器 .

    要执行此操作,请在“图像文件”选项卡中打开“gflags”(也可在上述包中找到),并为图像文件(服务)设置调试器可执行文件的路径;

    如果您的服务被标记为交互式(仅当它在SYSTEM帐户下运行时),您可以直接启动WinDbg,只需将调试器设置为类似 "PATH_TO_WINDBG\windbg.exe -g -G" (需要-g / -G,以便调试器不会中断执行在应用程序开始或结束时 - 默认行为) . 现在,在启动服务时,应该弹出windbg窗口并捕获任何未处理的异常 .

    如果您的服务不是交互式的,您可以在远程模式下启动NTSD调试器(命令行调试器)并从WinDbg连接到它(甚至可以在另一台PC上运行) . 为此,请将gflags中的调试器设置为 "PATH_TO_NTSD\ntsd -remote tcp:port=6666,server=localhost" . 然后通过使用 "windbg -remote tcp:port=6666,server=localhost" 之类的东西启动windbg连接到远程调试器,您应该完全控制其他调试会话 .

    至于找到异常的来源本身,一个windbg教程已经超出了这个主题,但作为一个开始尝试在捕获到异常后执行 "!analyze -v" 命令 - 幸运的是,这是您需要的所有信息 .

    Note: 也许这对你的情况来说太过分了,但是通过这种方法你甚至可以在系统启动期间调试服务(我曾经有一个服务的计时问题只有在第一次启动系统时出现问题)

  • 6

    有一点我do(可能是一种黑客行为)在 OnStart() 方法的开头放置 Thread.Sleep(10000) . 这给了我一个10秒的窗口,可以在我做任何其他事情之前将调试器连接到服务 .

    当然,当我完成调试时,我删除了 Thread.Sleep() 语句 .

    您可能做的另一件事是:

    public override void OnStart()
    {
        try
        {
            // all your OnStart() logic here
        }
        catch(Exception ex)
        {
            // Log ex.Message
            if (!EventLog.SourceExists("MyApplication"))
                EventLog.CreateEventSource("MyApplication", "Application");
    
            EventLog.WriteEntry("MyApplication", "Failed to start: " + ex.Message);
            throw;
        }
    }
    

    当您记录 ex.Message 时,您可能会收到更详细的错误消息 . 此外,您只需记录 ex.ToString() 即可获得整个堆栈跟踪,如果您的.pdb文件与可执行文件位于同一目录中,它甚至会告诉您异常发生在哪一行 .

  • 3

    在OnStart中添加大量详细日志记录 . 这是痛苦的老派,但它确实有效 .

  • 4

    似乎问题在于用户上下文 . 让我确认一下我的假设是否正确 .

    • 当您说代码从控制台应用程序完美运行时,我假设您正在您登录的同一用户下执行控制台应用程序 .

    • 当您说从Windows服务调用时相同的代码崩溃时,我认为该服务正在您的开发机器中的“本地系统”帐户中运行 .

    如果我的假设都是正确的,请尝试以下步骤 .

    • 在服务列表中,右键单击您的服务,选择属性,然后选择“登录”选项卡 .

    • 选择“此帐户”选项并提供现有用户名和密码 .

    • 现在尝试启动该服务 . 它应该现在开始没有任何错误 .

    以下可能是您的错误的根本原因

    • 如果您使用的是SQL Server,请确保未使用SSPI身份验证 .

    • 如果您尝试读取使用“本地系统”帐户时没有权限的任何共享文件夹\资源 .

    • 如果应用程序所需的任何所需依赖项位于“本地系统”用户无权访问的其他文件夹中 .

    • 如果您使用的VBA自动化无法在“本地系统”帐户中使用 .

    • 尝试禁用防火墙或防病毒软件 .

  • 37

    您可以在互操作调用周围添加一些日志记录,以找出哪个失败 .

    默认情况下,服务也与桌面无关;如果您打开services.msc控制面板小程序,获取服务的属性,请转到“登录”选项卡,您可以选中“允许服务与桌面交互” . 在某些情况下,这可以解决您的问题 .

  • 5

    我认为原因可能是因为大量使用了互操作 . 所以你需要以不同的方式解决这个问题 . 我建议使用相同的服务逻辑创建一个Windows或控制台应用程序,并确保它首先工作没有任何问题,然后你可能想要创建Win服务 .

  • 3

    调试服务是一种痛苦,特别是因为启动似乎是在许多问题出现时(至少对我们而言) .

    我们通常做的是尽可能多地将逻辑提取到具有start和stop方法的单个类中 . 这些类方法都是服务直接调用的方法 . 然后我们创建一个WinForm应用程序,它有两个按钮:一个用于调用start,另一个用于调用stop . 然后,我们可以直接从调试器运行此WinForm应用程序,看看发生了什么 .

    不是最优雅的解决方案,但它适用于我们 .

  • 21

    查看this question,其中讨论了如何在窗口服务中捕获未处理的异常 .

  • 13

    为了将调试器附加到Windows服务,需要首先启动它 . 可以在Windows事件日志中检查服务无法启动的原因 .

    之后,从Visual Studio Debug-> Attach To Process中连接调试器的过程非常简单 .

  • 9

    我所做的是由OnStart()实现,看起来像这样:

    _myBusinessObject = new MyBusinessObject();
    

    在构建业务对象之后,计时器和IPC处理程序完成所有真实(服务)工作 .

    这样做允许您创建一个Forms / WPF应用程序,在Form_Loaded处理程序中调用上面相同的代码 . 这样,调试Forms应用程序与调试服务完全相同 .

    唯一的问题是,如果您使用app.config值,则会有第二个app.config文件需要保持最新 .

  • 3

    在Service OnStart方法中使用以下代码:

    System.Diagnostics.Debugger.Launch();
    

    从弹出消息中选择Visual Studio选项

相关问题