【问题标题】:How do you debug a Windows Service?如何调试 Windows 服务?
【发布时间】:2011-07-06 14:23:06
【问题描述】:

我阅读了有关该主题的 MSDN 文章。引用:

因为服务必须从 在服务范围内 控制管理中心而不是来自 在 Visual Studio 中,调试一个 服务没有那么简单 调试其他 Visual Studio 应用类型。要调试服务, 您必须启动该服务,然后 将调试器附加到进程中 它正在运行。然后你可以 使用所有调试您的应用程序 标准调试功能 Visual Studio 的。

现在我的问题是我的服务一开始就无法启动。首先它崩溃了,然后说:

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

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

无法启动 MyServiceName 本地计算机上的服务。错误 1053:服务没有响应 中的启动或控制请求 及时的时尚

那么,我该如何调查/调试我的服务无法启动的原因?问题是我创建了一个控制台应用程序,该应用程序完全符合服务的功能并且工作正常。 (我的意思是我刚刚将OnStart() 方法和主循环的内容复制到了main)。

任何帮助将不胜感激。

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

【问题讨论】:

  • 您查看了事件查看器吗?
  • 你能发布你的 OnStart() 方法的源代码吗?
  • 这可能会有所帮助(取决于服务类型):codeproject.com/KB/dotnet/DebugWinServices.aspx
  • @Rick:这是一个巨大的项目, OnStart 调用了一些调用其他方法的方法等。我不能发布它。但我必须提一下,使用 VS2003 编译和安装时,具有相同代码的相同服务运行良好
  • @DumbCoder:我刚刚做到了。谢谢。不错的实用程序。但我没有发现任何有趣的东西。我跟踪了事件 ID,并按照建议检查了服务的返回码,它是 0...所以它没有告诉我任何信息

标签: c# c++ debugging windows-services


【解决方案1】:

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

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 不会传递此参数,因此您也可以将其用作服务而无需任何代码更改(前提是您源自 @987654322 @)

编辑:

与 Windows 服务的另一个区别是身份(这对于 InterOp 可能尤其重要) - 您要确保在“应用程序”模式和服务模式下以相同的身份进行测试。

为此,您可以在应用程序模式下使用模拟(如果有帮助,我可以发布一个 C# 包装器,但这可以很容易地用 Google 搜索)来使用您的 Windows 服务将在其下运行的相同身份,即通常是 LocalService 或 NetworkService。

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

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

【讨论】:

  • 这很漂亮,不知道你能做到。
  • 我在这个小费上就像一个在小费上的人。不错!
  • 我们对我办公室的服务使用同样的方法。
【解决方案2】:

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

System.Diagnostics.Debugger.Break();

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

【讨论】:

  • 如果不启动他怎么能做到这一点?
  • 问题是,如果你在你的代码中放置一个手动断点,它会让你在它开始的时候观察正在发生的事情,然后你就可以看到它失败的确切原因。
  • 您的调试器是否必须附加此行代码才能执行任何操作?
  • 你所做的就是保持你的项目窗口打开,然后像往常一样运行你的服务。它将弹出一个窗口,询问您要将调试器附加到哪个代码会话。只需选择您的项目,就可以开始了。
  • @Phil:不,请参阅msdn.microsoft.com/en-us/library/…:如果没有附加调试器,则会询问用户是否要附加调试器。如果是,则启动调试器。
【解决方案3】:

这是我从 C. Lawrence Wenham 那里偷来的,所以我不能真正相信,但您可以使用以下代码以编程方式将调试器附加到服务,而不会中断执行:

System.Diagnostics.Debugger.Launch();

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

【讨论】:

  • 很好的解释#if DEBUG
【解决方案4】:

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

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

如果您的服务被标记为交互式(只有在系统帐户下运行时才可能),您可以直接启动 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" 命令 - 有一些运气好,这就是你需要的所有信息。

注意:也许这对您的情况来说有点过头了,但是通过这种方法,您甚至可以在系统启动期间调试服务(我曾经遇到过服务的计时问题,只有在启动第一次用系统)

【讨论】:

    【解决方案5】:

    我做的一件事(这可能是一种 hack)是在我的 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 文件与可执行文件位于同一目录中,它甚至会告诉您发生异常的行。

    【讨论】:

      【解决方案6】:

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

      【讨论】:

      • 或者只是一个通用的try/catch 现在写保存消息/堆栈跟踪。
      【解决方案7】:

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

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

      2. 当您说从 Windows 服务调用相同的代码时崩溃,我假设该服务正在您的开发计算机的“本地系统”帐户中运行。

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

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

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

      3. 现在尝试启动服务。它现在应该可以正常启动了。

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

      1. 如果您使用的是 SQL Server,请确保您没有使用 SSPI 身份验证。

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

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

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

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

      【讨论】:

      • 这让我很开心。我在这个问题上苦苦挣扎了 2 天。终于可以启动服务了。谢谢吨!
      【解决方案8】:

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

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

      【讨论】:

      • 虽然这可能对调试有用,但重要的是要注意,允许服务与桌面交互被认为是不好的做法。不要为生产而做。
      【解决方案9】:

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

      【讨论】:

      • 对不起,我错过了你问题中的那部分。如果您将所有服务代码复制到 winform 应用程序并看到它的工作,那么它可能与未在服务中加载依赖的 DLL 有关,您可以验证权限吗?
      【解决方案10】:

      调试服务很痛苦,特别是因为启动似乎是许多问题出现的时候(至少对我们来说)。

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

      不是最优雅的解决方案,但它对我们有用。

      【讨论】:

      • 发帖人已经说过他这样做了(使用控制台应用程序)。
      • @RQDQ - 当然。我们发现使用表单更简单。将其作为一种选择似乎是合理的。
      【解决方案11】:

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

      【讨论】:

        【解决方案12】:

        为了将调试器附加到 Windows 服务,需要先启动它。服务启动失败的原因可以在 Windows 事件日志中查看。

        之后,从 Visual Studio Debug->Attach To Process 附加调试器的过程非常简单。

        【讨论】:

        • 正如 OP 所说,他的问题是服务运行时间不够长,无法附加调试器。他知道为什么 Windows 说服务无法启动;这无助于他修复错误,因为它太笼统了。
        【解决方案13】:

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

        _myBusinessObject = new MyBusinessObject();
        

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

        这样做可以让您创建一个在 Form_Loaded 处理程序中调用上述相同代码的 Forms/WPF 应用程序。这样,调试 Forms 应用程序与调试 Service 完全相同。

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

        【讨论】:

          【解决方案14】:

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

          System.Diagnostics.Debugger.Launch();
          

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

          【讨论】:

            【解决方案15】:
            【解决方案16】:

            第 1 步 - 将 #if 区域添加到您的 Program.cs

               static class Program
                    {
                        /// <summary>
                        /// The main entry point for the application.
                        /// </summary>
                        static void Main()
                        {
                            ServiceBase[] ServicesToRun;
                            ServicesToRun = new ServiceBase[]
                            {
                                new StockInfoService()
                            };
                            ServiceBase.Run(ServicesToRun);
                
                #if (!DEBUG)
                
                            ServiceBase[] ServicesToRun = new ServiceBase[] { new SqlBackupService() };
                                                                ServiceBase.Run(ServicesToRun);
                #else
                            StockInfoService service = new StockInfoService();
                            service.OnStart();
                            System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);
                #endif
                        }
            

            第 2 步 - 在 Service.cs 中更改您的 OnStart(string[] args) 方法,不带参数一。 (我赞扬了我的。)

                public void OnStart()
                
              //protected override void OnStart(string[] args)
                {
                    **Do your thing.
                }
            

            第 3 步 - 只需点击开始 (F5) 并调试您的代码。

            【讨论】:

              猜你喜欢
              • 2011-07-01
              • 2011-02-07
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多