Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / programming / debugging

Debugging a Start of a Windows Service

4.00/5 (1 vote)
22 Jun 2015MIT3 min read 15K  
This post is the second and final one dedicated to debugging .NET Windows services.

Introduction

This post is the second and final one dedicated to debugging .NET Windows services (you may read the first one here). The inwrap tool (presented in the first part) is not very friendly to use and I myself haven’t used it since then.:) It’s not the best advertisement of someone’s own work, but it did motivate me to write another tool which maybe will gain your interest. The winsvcdiag is a simple application that allows you to debug a start of a Windows service from Visual Studio (or any other debugger – even the remote one).

The idea is really simple. I again use the Image File Execution Options to hook upon a service executable. Let’s see how this works for a sample TestService whose logic is implemented in a testservice.exe executable. First, we need to create a Debugger value under the key HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\testservice.exe. You may either edit the registry manually or use a shortcut:

winsvcdiag --install testservice.exe

Whichever step you take, the result should look as in the image below (the path to the winsvcdiag may differ).

regedit-with-hook

From now, when the service is started by the services.exe process, Windows will first run the winsvcdiag.exe passing to it as a first argument the full path to the service executable. Winsvcdiag starts the process, but in a suspended state (using a special flag CREATE_SUSPENDED – the native part is copied from this CodeProject article):

C#
bool success = NativeMethods.CreateProcess(null, sargs, IntPtr.Zero, 
                    IntPtr.Zero, false, ProcessCreationFlags.CREATE_SUSPENDED, 
                    IntPtr.Zero, null, ref si, out pi);

and then waits in a loop for a debugger to appear:

C#
while (!isDebuggerPresent) {
   ...
   if (!NativeMethods.CheckRemoteDebuggerPresent(pi.hProcess, ref isDebuggerPresent)) {
       failuresCnt++;
       continue;
   }
   Thread.Sleep(1000); // sleep for 1s before the next check
}

As we are not really a debugger, we need to disable the hook while we are calling the CreateProcess function, otherwise winsvcdiag will call itself recursively. Now it’s time for you to set a breakpoint in a service initialization code and attach a debugger to the TestService process (it might run on a remote machine):

attach-to-process

In a moment, your breakpoint should be bound and then hit. From now, you may debug the service in the usual way. It is very important that you set the breakpoint before attaching to the service process. Otherwise, you may miss the method you would like to debug. After you are done with diagnosis, uninstall the hook using the –uninstall option – you may always check which hooks are installed with a –list option:

windbgsvc.exe --uninstall testservice.exe

When you debug the start method of a service, you don’t have much time – by default, the start method should finish within 30s and if it fails to do so, it will be killed by the system. As you can imagine 30s usually is not enough to resolve an issue. Fortunately, this timeout is configurable in the registry by the ServicesPipeTimeout value under the key HKLM\SYSTEM\CurrentControlSet\Control. It’s a dword which represents time in milliseconds the services.exe will wait for a service to start (it is called a pipe timeout as the services.exe process communicates with its child services using a pipe). Again, you may modify the registry manually or use the winsvcdiag.exe – the timeout parameter accepts the time in seconds:

PS > .\winsvcdiag.exe --timeout 120
Timeout changed, but reboot is required for the option to take an effect.
A path to the service exe file must be provided.

A reboot is required for this option to take effect.

What About Topshelf Services?

Topshelf is quite restrictive when it comes to its command line parameters. It also checks if its parent process is services.exe and if it is not (which is the case when we start the service from winsvcdiag), it will assume that it is running from the command line. To overcome those restrictions, I prepared the Topshelf.Diagnostics Nuget package. It contains an extension method for an improved parsing of the service command line as well as a changed check for the way in which the service is run (I assume that it’s not a command line mode if it’s run from a session zero). To apply those changes to your service, you just need to add two lines to the HostConfigurator initialization:

C#
private static void Main() {
    HostFactory.Run(hc => {
        ...
        hc.ApplyCommandLineWithDebuggerSupport();
        hc.UseWindowsHostEnvironmentWithDebugSupport();
        ...
    });
}

The code is available in my dotnet-tools github repo and the binaries can be found here.

Filed under: CodeProject, Diagnosing Applications on Windows

Image 3 Image 4

License

This article, along with any associated source code and files, is licensed under The MIT License