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

With an understanding of the types of commands WinDBG can execute, you can turn to the final set of commands: dump file commands. As I mentioned in "The Basics" section at the beginning of this chapter, WinDBG's forte is handling dump files. In this section, I'll discuss all the gyrations necessary to create and use dump files in WinDBG. The beauty of WinDBG and dump files is that nearly all the informational commands work on the dump files, so it's almost like you're right there at the time the problem occurred.

Creating Dump Files

At any point when you're doing live debugging, you can call the .DUMP (Create Dump File) command to create a dump file. Before mentioning anything about what can be in a dump file, I need to point out that it's up to you to specify the extension for any dump file you create. The .DUMP command will happily write the file exactly as you specify the complete name and path, without adding any missing file extension. The extension you should always use is .DMP.

With the extension issue out of the way, I want to discuss some of the general options the .DUMP command offers before jumping into the types of dump files. The first is the /u option, which will append the date, time, and PID to the filename so that you can get unique dump names without having to struggle with naming them. Since dump files are such great tools for taking snapshots of a debugging session so that you can analyze behaviors later, the /u option makes your life much easier. To provide a better idea of what was happening at a particular time, another option, /c, allows you to specify a comment that will be displayed as you load the dump file. Finally, if you're debugging multiple processes as at a time, the /a option will write out dump files for all loaded processes. Make sure you use the /u switch with /a to ensure every process gets a unique name.

WinDBG produces two types of dump files, full and mini. A full dump file includes everything about the process, from the current thread stacks to all memory data to even the actual binaries loaded in the process. A full dump is specified with the /f option. Although having the full dump file is convenient because there are fewer things you need to match up when loading it, you'll chew up a tremendous amount of disk space creating it.

On the minidump side, you can specify numerous options. To create a general minidump file, you just need to use the /m option, which happens to be the default if you don't specify any options to the .DUMP command. The produced minidump will be the same as the default minidump that Visual Studio .NET creates, and it will contain the loaded modules' versions and enough of the stack information to produce call stacks for all active threads.

You can also tell WinDBG to add more information to the minidumps by specifying various flags as part of the /m option. The most useful minidump option is h (/mh), which, in addition to the default minidump information, also writes the active handle information to the minidump. That means you'll be able to use the !handle command to view all handle states at the time the dump was created. If you think you'll be analyzing pointer problems in the dump file, you might also want to specify the i option (/mi) to have WinDBG include secondary memory. This option looks at pointers on the stack or backing store and records memory referenced by them as well as a small bit of memory around those locations. That way you can follow pointers to see what they contain. There are numerous other minidump options you can specify to record additional pieces of information, but the h and i options are the ones I always use.

One final option that can save you a ton of disk space is /b, which will compress the dump file into a .CAB file. This is a great option, but in the same way that a missing extension is problematic with the general .DUMP command, a missing extension is compounded when using the /b switch. Since .DUMP doesn't append an extension, your first instinct is to add .CAB to get the extension onto the file. However, by specifying the .CAB extension, WinDBG creates the temporary .DMP file with a name of <name>.CAB.DMP inside the actual .CAB file. Fortunately, WinDBG will read the oddly named .DMP file from the .CAB file just fine.

Even given these tiny problems with the .CAB writing option, I still like to use it. In addition to storing only the .DMP file in the .CAB, you can specify the /ba option when you'd like to also store the currently loaded symbols into the .CAB! If you want to ensure you get all the symbols for the process, make sure to execute a ld * (load all symbols) command before creating the dump file. This way you can be assured that you'll have the correct symbols when you take the .CAB to a machine that might not have access to your symbol store. Another thing to keep in mind with the /b option is that WinDBG writes the dump file and builds the corresponding .CAB file in the %TEMP% directory on the machine. As you can imagine, if you have a large process, use /f to create a full dump, and use /ba to create a .CAB with symbols, you'll need gobs of disk space free in the %TEMP% directory.

Opening Dump Files

Dump files aren't much good if you can't open them. The easiest way to open a dump file is from a new instance of WinDBG. On the File menu, select Open Crash Dump or press Ctrl+D to bring up the Open Crash Dump dialog box, and browse to the directory in which the dump file is located to open it. Interestingly, although it's not documented, WinDBG will open .CAB files that contain .DMP files directly as well. After opening the dump file, WinDBG automatically gets everything loaded so that you can start looking at the dump.

If you created the dump on the machine where you built the process, your life is very easy, as WinDBG will do a good job of getting the symbols and source information lined up. However, most of us will be analyzing dump files created on other machines and other operating system versions. After opening the dump file comes the work of getting the symbol, source, and binary paths set up.

Your first step is to determine which modules have symbol information missing by doing an LM command with the v option. If any modules report "no symbols loaded," you'll need to get the symbol path adjusted to find the symbols. Look at the version information associated with those modules and update the Symbol File Path (from the File, Symbol File Path menu) appropriately.

The second step is to get the image paths set. As I mentioned in the discussion of symbol servers in Chapter 2, WinDBG needs access to the binaries before it can load the symbols for minidumps. If you followed my recommendation and put your programs and the various operating system binaries and symbols into your symbol server, getting the binaries lined up is trivial. In the Executable Image Search Path dialog box, which is accessible by choosing Image File Path from the File menu, you can simply paste the same string you set for your symbol path. WinDBG will automatically search your symbol server for the matching binaries.

If you don't have the binaries in a symbol store, you're going to have to set the image path manually and hope you point to the correct versions to get them loaded. This is especially difficult with operating system binaries because a hot fix can change any number of binaries. In fact, every time you apply any hot fix or service pack, you should reload your symbol store by running my OSSYMS.JS file discussed in Chapter 2.

The final path you'll need to set up is the source path, which is accessible from the File menu, Source File Path option. After setting all three paths, you should force a symbol reload with the .RELOAD /f command followed by an LM command to see which symbols are still mismatched. If the minidump came from a customer site, you might not be able to get all the binaries and symbols loaded because that site might have different hot-fix levels or third-party products that jam DLLs into other processes. However, your goal is to get all your product's symbols loaded and as much of the operating system's symbols as possible. After all, having all the symbols loaded makes debugging easy!

Debugging the Dump

Once you've got the symbols and binaries properly loaded, debugging the dump file is almost identical to live debugging. Obviously, some commands such as BU won't work on dump files, but most will, especially the extension commands. If you're having trouble with a command, make sure to look at the environment table in the documentation for the command and verify that you can use it with dump files.

If you have a situation in which you created multiple dump files at the same time, you can also debug them side by side with the .OPENDUMP (Open Dump File) command. Once you open a dump file this way, you'll need to issue a G (Go) command so that WinDBG can get everything started.

Finally, one command that's only available when debugging dump files is the .DUMPCAB (Create Dump File CAB) command. This will create a .CAB file from the current dump file. If you add the –a parameter, all symbols will be written to the file.

Категории