Debugging Applications for MicrosoftВ® .NET and Microsoft WindowsВ® (Pro-Developer)

At this point, you've seen enough commands (representing only a fraction of the commands available) to make your head spin, and you're probably wondering why I'm spending this much time discussing WinDBG. WinDBG is harder to use than Visual Studio .NET and the learning curve isn't just steep—it's nearly vertical! You've seen that WinDBG offers some very cool breakpoint possibilities, but at this point, you're still probably wondering why the hassle is worth it.

WinDBG is worth the investment because of the extension commands. These are commands that extend the debugger and allow you to see things you can't see any other way. Microsoft has supplied a bunch of great extensions that will quickly make you a WinDBG convert. These extensions are what the ninja master debuggers are using to solve the nastiest problems in the business.

I'm going to concentrate on a few of the most important extension commands. I highly recommend that you take the time to read the documentation about all the rest of the extensions. Under the Reference\Debugger Extension Commands node in the WinDBG documentation are the two key sections, General Extensions and User-Mode Extensions.

Physically, the extensions are dynamic-link library (DLL) files that export specific function names to do the work. Under the Debugging Tools For Windows directory are several directories such as W2KFRE (Windows 2000 Free Build) and WINXP. Those directories contain the various operating-system versions of the extension commands. You can read about how to write your own extension in the README.TXT file that accompanies the EXTS sample in the <Debugging Tools for Windows Dir>\SDK\SAMPLES\EXTS directory.

Loading and Controlling Extensions

Before we start looking at individual extension commands, I need to talk about how you can see which extensions you've loaded, how to load your own, and how to get help from an extension. To see what extensions you have loaded, use the .CHAIN (List Debugger Extensions) command. This command also shows you the search order for commands from the top of the display down to the bottom as well as the path that WinDBG searches for the extension DLLs. Under Windows 2000, the display looks like the following (depending on the path to your Debugging Tools for Windows directory) for the four default user-mode extensions DLLs (DBGHELP.DLL, EXT.DLL, UEXT.DLL, and NTSDEXTS.DLL):

0:000> .chain Extension DLL search Path: G:\windbg\winext;G:\windbg\pri;G:\windbg\WINXP;G:\windbg; Extension DLL chain: dbghelp: image 6.1.0017.1, API 5.2.6, built Sat Dec 14 15:32:30 2002 [path: G:\windbg\dbghelp.dll] ext: image 6.1.0017.0, API 1.0.0, built Fri Dec 13 01:46:07 2002 [path: G:\windbg\winext\ext.dll] exts: image 6.1.0017.0, API 1.0.0, built Fri Dec 13 01:46:07 2002 [path: G:\windbg\WINXP\exts.dll] uext: image 6.1.0017.0, API 1.0.0, built Fri Dec 13 01:46:08 2002 [path: G:\windbg\winext\uext.dll] ntsdexts: image 5.2.3692.0, API 1.0.0, built Tue Nov 12 14:16:20 2002 [path: G:\windbg\WINXP\ntsdexts.dll]

Loading an extension is as simple as passing the name of the extension DLL as the parameter, without the .DLL extension, to the .LOAD (Load Extension DLL) command. Of course, to unload an extension DLL, pass the name of the extension to the .UNLOAD (Unload Extension DLL) command.

By convention, extension commands are all lowercase, and unlike regular and meta commands, are case sensitive. Also by convention, any extension DLL is supposed to provide a command named, appropriately, help to give you a quick clue as to what's in the extension DLL. With the default extensions loaded, issuing the !help command doesn't show all the help available. To call an extension command out of a specific extension DLL, you append the DLL name and a period to the extension command: !dllname.command. Therefore, to see the help out of NTSDEXTS.DLL, the command is !ntsdexts.help.

The Important Extension Commands

Now that you're armed with a little background on how to deal with extensions, I want to turn to the important extension commands that will make your life easier. All these extensions are part of the default extension set which is always loaded, so unless you specifically unload any of the default extensions, these commands will always be available.

The first important command is the !analyze -v command, which allows you to get a quick analysis about the current exception. I specifically showed this command with the –v parameter because without it, you don't see much information. The !analyze command won't automatically solve all your bugs, but the idea is that it will give you all the information you'd normally want to see at the time of the crash, such as the exception record and the call stack.

Because critical sections are lightweight synchronization objects, many developers are using them. WinDBG offers two extension commands for getting a peek inside the critical section to see the objects' lock state and which thread owns them. If you have the address of the critical section, you can view the extension by passing the address as a parameter to the !critsec command. If you want to see all locked critical sections, the !locks command allows you to do just that. If you want to see all critical sections in a process, pass the –v parameter to !locks. On Windows XP and Windows Server 2003, the additional –o parameter will show you orphaned critical sections.

If you're doing Win32 security programming, it's rather difficult figuring out what the current security information applied to the current thread is. The !token (Windows XP/Windows Server 2003) !threadtoken (Windows 2000) command will show the impersonation state of the current thread as well as all sorts of other security information such as the user's identity and any groups, plus a textual display of all privileges associated with the thread.

One extension command that has saved me countless hours debugging is the !handle command. As you can tell by the name, the command has something to do with the handles in a process. Just typing !handle by itself will show you the handle values, what type of object that handle contains, and a summary section listing how many of each type of object is in the process. Some of the types displayed might not make sense if you haven't done device drivers or read David Solomon and Mark Russinovich's Inside Microsoft Windows 2000. Table 8-3 provides a translation from the !handle command into user-mode terminology for some of the types you'll see.

Table 8-3: Handle Type Translations

!handle Term

User-Mode Term

Desktop

Win32 desktop

Directory

Win32 object manager namespace directory

Event

Win32 event synchronization object

File

Disk file, communication endpoint, or device driver interface

IoCompletionPort

Win32 IO completion port

Job

Win32 job object

Key

Registry key

KeyedEvent

Non-user-creatable events used to avoid critical section out of memory conditions

Mutant

Win32 mutex synchronization object

Port

Interprocess communication endpoint

Process

Win32 process

Thread

Win32 thread

Token

Win32 security context

Section

Memory-mapped file or page-file backed memory region

Semaphore

Win32 semaphore synchronization object

SymbolicLink

NTFS symbolic link

Timer

Win32 timer object

WaitablePort

Interprocess communication endpoint

WindowStation

Top level of window security object

Just showing you the handle values is great, but if you pass the -? parameter to !handle, you'll see that the command can do a whole lot more. If you want to see more information about a handle, you can pass that handle value as the first parameter and, in the second parameter, a bit field that specifies what you want to see from that handle. In the second parameter, you should always pass F because that will show you everything. As an example, I'm debugging the WDBG program from Chapter 4, and the handle 0x1CC is an Event. The following shows how to retrieve the detailed information about that handle:

0:006> !handle 1cc f Handle 1cc Type Event Attributes 0 GrantedAccess0x1f0003: Delete,ReadControl,WriteDac,WriteOwner,Synch QueryState,ModifyState HandleCount 3 PointerCount 6 Name \BaseNamedObjects\WDBG_Happy_Synch_Event_614 Object Specific Information Event Type Manual Reset Event is Waiting

Not only do you see the granted access information, but you see the name and, more important, that the event is waiting (meaning it's not signaled). Since the !handle command will show this information for all types, you now have the ability to easily look for deadlocks because you can check the states of all events, semaphores, and mutexes to see who's blocked and who's not.

You can look at all the detailed handle information for every handle in a process by passing 0 and F as the two parameters. If you're working on a large process, it might take a while to grind through all the details. To see just the details for a particular class of handles, pass 0 and F as the first two parameters, and for the third parameter, pass the handle class value. For example, to see all the events, the command is !handle 0 f Event.

In the preceding discussion I mentioned using !handle to view event states so that you can deduce why your application is deadlocking. Another wonderful use of !handle is to keep an eye on a potential resource leak. Since !handle shows you the complete count of all current handles in your process, you can easily compare before and after !handle snapshots. If you see the total handle counts change, you'll be able to tell exactly which type of handle was leaked. Because much of the detailed handle information is displayed, such as registry keys and the name of the handle, you can easily see exactly which handle you're leaking.

I've tracked down countless resource leaks and deadlocks with the !handle command and I strongly encourage you to spend some time familiarizing yourself with it and the data it shows. The !handle command is the only way you'll get handle information while debugging, so it's extremely valuable in your bag of tricks.

Common Debugging Question: The Win32 API functions that create handles, like CreateEvent, have an optional name parameter. Should I be assigning a name to my handles?

Absolutely, positively, YES! As I pointed out in the discussion of the WinDBG !handle command, the command can show you the states of each of your handles. However, that's only a small part of what you need to find problems. If the handles aren't named, it's very hard to match up handle values with debugging challenges such as deadlocks. By not naming your handles, you're making your life much more difficult than it needs to be.

However, you can't just go off and start slapping names in the optional parameter field. When you create an event, for example, the name you give that event, such as "MyFooEventName," is global to all processes on the machine. Although you might think that the second process, which calls CreateEvent, is getting a unique event internally, in reality, CreateEvent calls OpenEvent and returns to you the globally named event handle. Now suppose you have two of your processes running and they each have a thread waiting on the MyFooEventName event. When one of the processes signals the event, both processes will see the signal and start running. Obviously, if you intended to signal only one process, you just created an extremely difficult bug to track down.

To properly name handles, you'll have to ensure you generate unique names for those handles that you want available only in a single process. Look at what I did in WDBG in Chapter 4. I appended the process ID or the thread ID to the name to ensure uniqueness.

Other Interesting Extension Commands

Before I move on to dump file handling, I want to mention several extension commands that you'll find useful in critical situations, like when you need to solve that one really challenging bug. These are the kind of commands that when you need them, you really need them. The first interesting one was !imgreloc. It simply runs through all loaded modules and tells whether all modules are loaded at your preferred address. Now you have no excuse for not checking. The output looks like the following. The operating system had to relocate the second module, TP4UIRES.

0:003> !imgreloc 00400000 tp4serv - at preferred address 00c50000 tp4uires - RELOCATED from 00400000 5ad70000 uxtheme - at preferred address 6b800000 S3appdll - at preferred address 76360000 WINSTA - at preferred address 76f50000 wtsapi32 - at preferred address 77c00000 VERSION - at preferred address 77c10000 msvcrt - at preferred address 77c70000 GDI32 - at preferred address 77cc0000 RPCRT4 - at preferred address 77d40000 USER32 - at preferred address 77dd0000 ADVAPI32 - at preferred address 77e60000 kernel32 - at preferred address 77f50000 ntdll - at preferred address

If you're too lazy to run an MS-DOS box in order to run NET SEND so that you can send messages to other users, you can simply enter !net_send. Actually, this comes in handy when you need to get someone's attention when doing remote debugging. Entering !net_send by itself will show you the parameters necessary to send the message.

Whereas you have !dreg to display registry information, you have the !evlog to display the event log. If you enter both of these commands on the command line by themselves, you'll get help on how to use them. Both are wonderful for helping you see what you're about to read from the registry or event log. If you use them, especially when remote debugging, you won't have any surprises.

If you're having issues with exception handling, you can use the !exchain command to walk the current thread's exception handling chain so that you can see exactly which functions have registered exception handlers. The following shows the output when debugging the ASSERTTEST.EXE.

0012ffb0: AssertTest!except_handler3+0 (004027a0) CRT scope 0, filter: AssertTest!wWinMainCRTStartup+22c (00401e1c) func: AssertTest!wWinMainCRTStartup+24d (00401e3d) 0012ffe0: KERNEL32!_except_handler3+0 (77ed136c) CRT scope 0, filter: KERNEL32!BaseProcessStart+40 (77ea847f) func: KERNEL32!BaseProcessStart+51 (77ea8490)

For dealing with operating system heaps (those heaps created with the CreateHeap API), you have the !heap command at your disposal. You might not think you're using any operating system heaps, but the operating-system code running inside your process is making considerable use of them. If you corrupt memory from one of those heaps, which you'll learn more about in Chapter 17, you can use the !heap command to figure out which heap was corrupted and where it was corrupted.

Finally, I want to mention a very interesting and useful undocumented command, !for_each_frame, from the EXT.DLL extension. As you can tell from the name, this instruction will execute a command string passed as a parameter for each frame up the stack. The perfect use of this command is !for_each_frame dv, which will show each frame's local variables up the stack.

Категории