Debugging Services

[Previous] [Next]

As you've seen, the unique nature of services alone means that you need to deal with many issues that don't come up when you're programming regular user-mode applications. Keep in mind that the discussion so far has been about the bare minimum functionality necessary for getting a service up and running. I haven't even touched on the fundamental requirement of ensuring that your general algorithms and implementation work for the one-of-a-kind pieces of your service. The easiest and best way to debug services without becoming completely overwhelmed is to approach the debugging in stages.

Three main stages are involved when you're debugging services:

  • Debugging the core code
  • Debugging the basic service
  • Debugging for real

In the following sections, I'll describe what you need to do in each stage to make sure that you stand the best chance of debugging your service with a minimal amount of hassle.

Debugging the Core Code

Before you even consider running your application as a service, you need to run and test your application as a standard user-mode executable until you've debugged all your core code. Once you've done that, you can start working on the issues specific to services.

When debugging your core code, you should debug everything on the same machine, running under your developer account; that is, your service core code as well as any client code should be on the same machine. With this approach, you won't have to worry about any security or network issues. Once you debug your logic, you can move on to the joy of other problems you'll encounter with services, such as security and service initialization orders.

COM+ Services

If you're building a COM+ service with the Active Template Library (ATL), such as the TraceSrv utility in Chapter 11, you don't have to do anything with security. By default, ATL runs as a user-mode executable until you register your application with the -Service command-line option.

ISAPI Filters and Extensions

The exported functions you must provide for your filters and extensions are fairly simple, and you can easily write a test harness that acts as a fake IIS system. You can test all your core algorithms in a controlled environment so that you have them completely debugged before you run your service under IIS proper.

Exchange Server

You can build Exchange service applications that run as console applications if you use the helper functions in WINWRAP.LIB. Starting your service with the notserv startup parameter will force running as a normal process. The notserv parameter must be the first parameter specified.

Debugging the Basic Service

After you test and debug your general logic, you can start debugging your code while running as a service. All your initial debugging should take place on a system on which you can control everything. Ideally, you should have a second machine sitting right next to your main development machine that you can use for your initial debugging. The second machine should have the version and flavor of Windows you're recommending to your customers for the environment your service will run under. Whereas the reason for debugging the core code was to verify your basic logic, the reason for preliminary service debugging is to shake out your basic service-specific code. You need to complete four tasks as part of debugging your first-cut service code:

  • Turn on Allow Service To Interact With Desktop.
  • Set your service identity.
  • Attach to your service.
  • Debug your startup code.

As I discuss each task, I'll mention particular issues relevant to the different technologies as appropriate.

Turn On Allow Service To Interact With Desktop

No matter what type of service you're debugging, you'll want to turn on Allow Service To Interact With Desktop on the Log On tab of your service's Properties dialog box. Although you shouldn't have any user interface elements with your service, having assertion message boxes that allow you to gain control with the debugger is very helpful. Assertion message boxes combined with excellent logging code, such as the code that ATL gives you for writing to the Event Log, can make it much easier to debug services.

In the initial stages of development, I turn on SUPERASSERT message box assertions just so I can quickly gauge the general health of my code. (For more information on SUPERASSERT, see Chapter 3.) As I get more and more of the service running, however, I set the assertion options so that all assertions just go through trace statements.

Until I'm confident of the service code, I generally leave the Allow Service To Interact With Desktop setting checked. One nasty bug that cropped up in a service I once wrote took me a while to track down because I turned off this option and I still had a message box assertion that popped up. Because the operating system security won't allow normal services to show a message box, my service just appeared to hang. Before I do turn off Allow Service To Interact With Desktop, I double-check that my service—and any DLLs that it uses—don't call message boxes by using DUMPBIN /IMPORTS to verify that neither MessageBoxA nor MessageBoxW are imported when I don't expect them to be.

Set Your Service Identity

To avoid security problems when you're trying to get your service running, you can set the identity of your service. By default, all services run under the System Account, which is sometimes referred to as the LocalSystem account. However, you can set your service to start under a user account with higher security access, such as someone who is a member of the machine's Administrator group—like you, for example!

In your service's Properties dialog box, click on the Log On tab. Select the This Account radio button, click the Browse button, and choose the appropriate account from the Select User dialog box. After selecting the user, you'll need to type in and confirm the password for that account. For COM+ services, DCOMCNFG.EXE can also set the logon identity of your service if you're more comfortable using it.

Attach to Your Service

Once your service has started, debugging isn't usually that difficult. All you need to do is to attach to your service process from the Microsoft Visual C++ debugger. Depending on your service and the complexity of your code, attaching to the service with a debugger might be all you need to do to debug. Follow these simple steps to attach to an active process from the Visual C++ debugger:

  1. Start MSDEV.EXE.
  2. From the Build menu, select Start Debug to bring up the pop-up menu, and then click Attach To Process.
  3. Check the Show System Processes check box so that you can see all the service processes.
  4. Select the process you want to debug from the list, and click OK.

An alternative method of attaching the debugger is just to call the DebugBreak API function. When the Application Error dialog box pops up, simply click the Cancel button and debug as you normally would. Keep in mind that if you're building a COM+ service, you should make the DebugBreak call outside any COM method or property invocations. If you don't, COM will eat the breakpoint exception generated by DebugBreak and you'll never get a debugger attached. In addition, you shouldn't call DebugBreak as part of your service's initial startup code; see the section "Debug Your Startup Code" for reasons why.

Yet another means to attach the debugger to your service is to use Task Manager. Bring up Task Manager, select the Processes tab, right-click on the process you want to debug, and select Debug from the pop-up menu. The operating system makes it easy to attach your debugger if you know what process you want to debug.

If the Debug option on the right-click menu is disabled in Task Manager, don't be alarmed—you're just seeing Windows 2000 security having some fun. Only users who are authenticated as Administrators on the local machine are allowed to attach a debugger to services. If the software engineers in your company typically log on with a domain account, you should add that domain account to the Administrators group on each of their machines.

IIS ISAPI filters and extensions

Internet Information Services 5 changed the rules about where ISAPI filters and extensions execute. In previous versions of IIS, all filters and extensions ran inside INETINFO.EXE, the main IIS service. In IIS 5, extensions run in DLLHOST.EXE because of the new pooled out-of-process model. ISAPI filters still run inside the IIS process, INETINFO.EXE. The new model makes IIS much more stable, and according to Microsoft, much more scalable. The only problem for debugging is that you might not know which DLLHOST.EXE process your extension is running under.

The new pooled out-of-process model for extensions applies only to Web sites created after you upgrade to IIS 5. If you upgrade your existing server to IIS 5, your existing Web shares will run extensions as they were with IIS 4. If your extension is handling its own thread pooling or using any form of RevertToSelf, you'll need to set up your extension to run inside IIS's address space. Search MSDN for the topic "Pooled Out-of-Process Model for ISAPI" to learn more about setting up your extension.

The IIS documentation also mentions that you should set up your extensions to run inside IIS so that you can debug them. The only problem with changing where your extensions run is that you should deploy your extensions so that you use the pooled out-of-process model. Because I'm a believer in debugging what you ultimately run, I want to show you the trick to debugging extensions even when they're running under DLLHOST.EXE, which is how your extensions will run.

Before I talk about using the debugger, though, I need to talk about how to figure out which process is running your filter or extension because multiple instances of DLLHOST.EXE will be running. First, you need to download a fantastic free utility, HandleEx for Windows NT, from Mark Russinovich and Bruce Cogswell's Web site, www.sysinternals.com. HandleEx will show you the handles that a process has open and, most important, which DLLs are loaded into which processes. To find your DLL using HandleEx, press F3 and type the filename of your DLL in the HandleEx Search dialog box. Click the Search button, and HandleEx will list the names and process IDs (PIDs) of the processes that have your DLL loaded. Figure 10-1 shows HandleEx reporting which process the Simple extension is running under.

click to view at full size.

Figure 10-1 HandleEx finding an IIS 5 extension running in the out-of-process pool manager

After you have the PID, you can attach the Visual C++ debugger to the process using the Attach To Process command. Keep in mind that once you attach to the process with the debugger, you must keep the debugger running even when you finish debugging your particular DLL. If you shut down the debugger, the process you're debugging will stop.

Because you're looking for a loaded DLL, you obviously have to make sure that it loads before you can debug it. Filters run inside INETINFO.EXE, so you can't attach the debugger before the IIS service starts. Therefore, you're out of luck if you want to debug the initialization. If you're debugging extensions, you can debug your initialization if you're ingenious. The idea is to create a dummy extension that you force IIS to load by connecting to your Web site with Microsoft Internet Explorer, which will make IIS start the DLLHOST.EXE pooled out-of-process executable. After you hunt down the PID for the new DLLHOST.EXE, you can attach the debugger. You can then set a breakpoint on LdrpRunInitializeRoutines so that you can step directly into your extension's DllMain. Matt Pietrek clarifies exactly what you need to do to set the LdrpRunInitializeRoutines breakpoint in his "Under the Hood" column from the September 1999 Microsoft Systems Journal. After you've set the breakpoint, you can load your real extension with Internet Explorer and debug your initialization.

Debug Your Startup Code

The hardest part of debugging services is debugging the startup code. The SCM will wait only 2 minutes under Windows NT 4 or 30 seconds under Windows 2000 for a service to get started and call StartServiceCtrlDispatcher to indicate that the service is running fine. Although both times are almost a lifetime on the CPU, you can easily spend that amount of time single-stepping through your code looking at variables.

The only clean way to debug your service startup code is to use trace statements if all you have is the Visual C++ debugger. Using Mark Russinovich's DebugView/Enterprise Edition, which I refer to in Chapter 3, you can see the statements as your service rolls along. Fortunately, your service startup code is generally more lightweight than the main service code, so debugging with trace statements isn't too painful.

The SCM timeout limits can cause problems for services that can't start quickly. A slow piece of hardware or the nature of your service can sometimes dictate a long startup time. The SERVICE_STATUS structure you pass to SetServiceStatus has two fields, dwCheckPoint and dwWaitHint, which might help you if your service is likely to time out on startup.

When your service does start, you can tell the SCM that you're entering the SERVICE_START_PENDING state, place a large hint in the dwWaitHint field (the time is in milliseconds), and set the dwCheckPoint field to 0 so that the SCM won't use the default times. If you need more time in your service startup, you can repeat the call to SetServiceStatus as many times as necessary as long as you increment the dwCheckPoint field before each successive call.

The final point I want to address about debugging startup code is that the SCM will add entries to the Event Log explaining why it can't start a particular service. In the Event Viewer, look in the Source column for "Service Control Manager." If you also use the Event Log for your lightweight tracing, between the SCM entries and your tracing, you should be able to solve many of your startup problems. If you use the Event Log, make sure your service dependencies are set so that your service will be started after the Event Log service.

Debugging for Real

The limitations of GUI debuggers make debugging services an interesting challenge. Even so, though you might find it a little painful, you can debug your services with the debuggers that come with Microsoft Visual Studio or the Platform SDK.

If you're developing services that have complicated processing requirements with heavy Win32 synchronization or memory sharing, or DLLs that load into services such as IIS, you might want to consider using Compuware NuMega's SoftICE debugger. SoftICE runs between the CPU and the operating system and can debug user-mode code with ease. Because GUI debuggers sometimes don't allow you to debug what you need to debug, such as the service startup or cross-process communications, SoftICE can make your debugging chores much easier. With SoftICE, you simply load your module's source code and set a breakpoint, and no matter how or where your module loads into memory, you can concentrate on debugging without worrying about the issues associated with GUI debuggers. Granted, SoftICE has a much steeper learning curve than the Visual C++ debugger, but if you're developing services and DLLs that load into services, you'll save yourself considerable amounts of time in the end if you take the time to learn how to use it.



Debugging Applications
Debugging Applications for MicrosoftВ® .NET and Microsoft WindowsВ® (Pro-Developer)
ISBN: 0735615365
EAN: 2147483647
Year: 2000
Pages: 122
Authors: John Robbins

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net