In the days of XP, a Windows Service was more or less an ordinary executable running in the same session as all other executables. Debugging it was fairly simple, although you did have to deal with the complication that it was started by the Service Control Manager.
Today Windows Services run in a mysterious Session 0, which is difficult to work with. If your Service is written in C++, you’ll find that it can be problematic to deal with bugs, particularly crashes, that occur during Service startup.
In this article I’ll explain why that is, and show you a very simple set of techniques for dealing with this problem. I hope that the relative simplicity of all this will reduce your frustration level when debugging your Windows Services.
The Problem Statement
In the XP days, Windows Services ran in the same environment as normal executables. Services handle huge amounts of the workload involved in keeping Windows humming along, and much of this stuff can drastically change the way the O/S behaves - which means any surface area they expose represents a real security issue.
When Windows Services are normal executables, they can be bombarded with Windows Messages, COM requests, DDE, all the normal IPC stuff that Windows uses. Malformed instances of these messages can crash services or get them to behave improperly. It’s generally just not a good way to do things - it would be like keeping the keys to the family jewels on a hook in your entry hall. The capture below shows just a small fraction of the services running on my Windows 7 laptop. If you are a black hat hacker, you can’t help but drool a bit at what you see there:
A Sampling of Maybe 120 Services On an Windows 7 System
In the Vista era, all services were moved to a special Session 0. (See Sessions, Desktops and Windows Stations for discussion of these terms.) Or to be more accurate, everything else was moved out of Session 0. Basically, this drastically limited the ability of user mode programs to interface with services - mostly for the good.
In general, executables running in Session 0 don’t communicate with your desktop session - no windows messages, for example, which makes them much more secure. But it also makes it hard to debug them. I’ll give some explanation below showing why this is, and how we get around it.
Debugging a Service
Unlike a normal application, I don’t start a service by entering its name on the command line, or
Instead I have to rely on the Service Control Manager to start and stop the process by having my
process respond to some very specific commands.
This doesn’t fit very well into the normal debugging paradigm - we normally expect the debugger to actually start the program in question. But with Windows Services, we must go to the Services plugin of the Management Console and tell it to start the service - the app then starts without much help from us.
To debug that app after it has started, you will need to invoke the Debug|Attach to Process function, which brings up the dialog shown below:
The Attach to Process Dialog
By checking the Show Processes From All Users checkbox, I can see my service, select it, and attach it to the debugger. I’m now free to set breakpoints, watchpoints, examine variables, and do all the other things that I need to debug an app. Things are just the way I want to them to be.
So What's the Problem?
It seems like we have a pretty reasonable way to debug a service, right? So why is this article even being written?
As it happens, I work on an app that runs as a Windows Service, and this app spends a lot of time at startup figuring out how it is configured. From time to time, things go wrong during that startup and my state is incorrect. Even worse, there are times when that startup code crashes.
As a toy example, here is some code that I might execute in the startup of a service. It runs at
the start of
PreMessageLoop(), a good place to do initialization of a service:
It turns out that I don’t check for the proper opening of the registry key - and I had
inadvertently stored the key in
HKLM/SOFTWARE/mrn/mrnService on my Windows 7 system. I
should have created it in
HKLM/SOFTWARE/WOw6432Node/mrn/mrnService. As a result, the
key open failed, and
checks_per_scond value was left at 0. (I don’t check for illegal
values of the key even if it is read properly - a second representative error.)
When I attempted to start this service, I’d get a dialog box from the Service Control Manager, something like this:
Error From Starting a Service
This would seem like a good point to attach to the service and start debugging, but you can forget about it - the service is already crashed and gone.
Where is JIT When I Need It?
What I really need here is the normal popup that I see on a dev system when a crash occurs - the one that asks if I would like to debug the troubled process. Why aren’t I seeing it?
If you are quick on the draw, Process Explorer actually shows you what is going on. In the screen
shot below, you can see that my service,
mrnService.exe is caught by the Windows
Error Reporting tool, which normally brings up just that dialog:
Windows Error Reporting Tools
The problem is that this is all happening in Session 0, which does not have the ability to interact with my desktop. So Windows Error Reporting pops up a dialog and quickly sees that there is nobody home to click on it. It simply closes up shop and kills the errant process. I have no opportunity to catch this in progress.
(This capture also highlights part of the difficulty in debugging services - the process has been started by
svchost.exe, not Visual Studio. The lifecycle of a service requires this, like it or not.)
A Reasonable Solution
It’s not quite true that Session 0 has no opportunity to communicate with your desktop. Windows
has a Remote Desktop API that allows for just the type of communications we would like. In
particular, I can use the
WTSSendMessage function to pop up a message on my desktop
when the service enters that crucial startup phase. The resulting message box gives me an
opportunity to attach to the service and start debugging before it has done anything of importance.
The only thing I need to send a message to my screen is the console session ID, and Microsoft was kind enough to provide an API for that as well. The lines inserted into my app to support this look like this:
With this in place, when I am unsure about what is happening with the startup, my workflow works like this:
I build my project, and install the service with
mrnService.exe /service. I set a breakpoint in my program at the the first line of code after
WTSSendMessage- the point where initialization commences. If I am drilling down with more specificity I might set the breakpoint at a different point in the startup code. I start the service with
sc start mrnService. I see a dialog popup as shown below. I switch to Visual Studio, and invoke Debug|Attach to Process. In the resulting dialog, I make sure that I have Show processes from all users checked. I navigate down to
mrnService.exeas quickly as possible, then click the Attach button. Finally, I return to the dialog popped up by my service and click OK. If all is working properly, I will then immediately hit a breakpoint in my service, and I can start walking through my code to see where things have gone awry.
My Service Startup Popup
This is simple and works more or less as expected. Compared to other techniques for debugging service startup, I find it a winner.
A Few Other Issues
Microsoft really blew it when they decided to name this special class of processes Windows Services. The word Services is so heavily overloaded in the world of Windows that you could easily come up with a dozen different uses for it - starting with the biggest conflict, COM Services/Servers. This makes it extraordinarily hard to use web searches to find information on your problem.
*IX is way out ahead on this, with their equivalent processes having the unique name daemons. The search space is much more constrained, allowing you to drill down a lot faster.
One other problem you might run into when debugging services is that the Service Control Manager will run out of patience when trying to start your service. After a minute or two, you are going to fail out. Buried in MS KB824344 you can find the instructions on modifying your registry settings to allow for long timeouts - not something you want to do on production machines, but totally appropriate in a development world.
My demo 32-bit C++ Windows Service project for Visual Studio 2012 is here. This is just a shell and doesn’t do much, but you can experiment with it to do sanity testing.
If you are using RDP to connect to the machine where you are debugging, the technique shown here won’t quite work. It will pop up a dialog on the console of the machine you are connected to - not the RDP window that you are using. I’ll show you how to get around this problem in a future article.
None of this stuff matters if you are developing on XP. Of course, being on XP means you have plenty of other problems to deal with, but Session 0 isolation is not one of them.
I also highly recommend not using the
mmc.exe Services plugin to start and stop your
services during development - in fact it is best if you don’t even run
you are doing service development. There are issues you will run into in which your services
become disabled but can’t be removed, sometimes even requiring a reboot. Open an escalated
CMD.EXE window in the Debug directory of your project, and get used to using these
commands over and over:
rem rem initial registration of your service rem servicename.exe /service rem rem stop and start service rem sc start servicename sc stop servicename rem rem Removal of service rem servicename.exe /unregserver
Of course, keep in mind that the name of your executable and the name of your service won’t necessarily be the same. That’s up to you.
Good luck with your service development! Once you get past some of these tricky bits, it’s really no different than debugging your normal apps. You just have to become a bit of a startup guru.