Code for this article: Hood0497.exe (5KB)
Matt Pietrek is the author of Windows 95 System Programming Secrets (IDG Books, 1995). He works at NuMega Technologies Inc., and can be reached at email@example.com.|
I want to implement an exception handler that can be used as the default exception handler for each thread. The exception handler should record two kinds of information: variable information provided by the failing application and system information such as the name of the routine where the exception occurred and a (symbolic) call tree of how it got to that point in the code.
via the Internet
This is an excellent question, because to answer it I'll have to cover a variety of topics that readers have expressed interest in. In fact, there's so much to cover that I've split the answer into two parts. This month, I'll set up a basic framework for intercepting unhandled exceptions and writing a report file with some basic information. I'll include some simple code for walking the stack on the Intel platform, which is something I'm asked about often. Next month, I'll go into the various types of debug information and describe what they're used for. From there, I'll show you how to use IMAGEHLP.DLL to access debug information and walk the stack in a portable manner.
The first question that needs to be addressed in writing this code is how to receive control when an unhandled exception occurs. By unhandled I mean that none of the program's installed exception handlers (for example, _try blocks in C++) have elected to handle the exception. The exception is destined to go to the operating system, which will render the death penalty to the errant process. In Windows® 3.x, you could request to be notified of certain exceptions that occurred in any task in the system with the InterruptRegister API from TOOLHELP.DLL. Diagnostic programs like Dr. Watson were written to sit in the background and wait for "bad" exceptions to occur in other tasks. When they occurred, the diagnostic program sprang into action and recorded all sorts of information about the doomed task.
By design, Win32® doesn't let you arbitrarily monitor exceptions in other processes. The only exceptions your process can see are its own and exceptions for which it is acting as a debugger. This latter case is how programs like DRWTSN32.EXE and "just in time" debuggers work. Based upon the AeDebug key in the registry, the system invokes a program (DRWTSN32.EXE, for example) that attaches to a faulting task as a debugger. DRWTSN32.EXE uses the magical powers of Win32 debuggers to probe the faulting process and generate a report about the state of the process when it faulted.
Although DRWTSN32.EXE and "just in time" debuggers are great for application developers, users may not have them installed, and even if they do, the information that something like DRWTSN32 provides may not be everything they need to know. The solution is to dispense with the debugger approach and grab the unhandled exception within your own code. With this approach, you can tailor the exception report to include whatever information you desire, and make that code a part of your application.
To get back to the original question, the key to grabbing unhandled exceptions is the SetUnhandledExceptionFilter API. Using this API, a program can install a callback that will be invoked whenever a regular exception handler (for example, a _try/_except block) doesn't handle an exception. As I showed you in my January 1997 MSJ article on structured exception handling, the callback is invoked as part of KERNEL32's UnhandledExceptionFilter API. The parameter for your callback function is a pointer to an EXCEPTION_POINTERS struct. The information that can be obtained from this structure consists of information about the exception and the CPU registers at the time of the exception. As I'll soon show, there's quite a bit you can learn from just this information.
Using the SetUnhandledExceptionFilter API as the basis for what I needed to do, I set out to design an exception reporting framework (see Figure 1). The first criterion was that my framework shouldn't require any changes to the application's source code. Likewise, it shouldn't require you to haul around an additional DLL. This implies that the framework code should be linked into your EXE. My second criterion was that the framework should avoid using library calls from the application's runtime library since the library's data might be trashed at the time of an exception. Therefore, the framework uses only Win32 API calls and a few "safe" C++ runtime library calls. For fancy output formatting, I implemented a simple version of printf using the Win32 API. Finally, because I'm somewhat of a programming masochist, I made the code compilable as either ANSI or Unicode.
Fulfilling the criterion of not requiring any source code changes was easy. I defined a C++ class called MSJExceptionHandler. In the source file containing the class's member functions, I also declared a single global instance of
the class. By simply linking the resulting OBJ file into
my project, I've put the framework in place. I put the code that calls SetUnhandledExceptionFilter in the class's
constructor. Since the single instance of the class is global in scope, its constructor is called automatically when the program starts.
After the MSJExceptionHandler constructor executes, you've got a callback function, MSJExceptionHandler::MSJUnhandledExceptionFilter, that is invoked whenever an unhandled exception occurs. The first order of business in this function is to open up the file that the exception report will be appended to. By default, the report file has the same name as the executable file, but with an .RPT extension. If you don't like this name, you can call MSJExceptionHandler::SetLogFileName to override it. It's worth noting that I opened the file with the FILE_FLAG_WRITE_
THROUGH attribute. In case the exception report generation code itself blows up, at least the data already written should be safely on the disk.
Assuming the report file opened OK, the MSJUnhandledExceptionFilter method seeks to the end of the file and calls the GenerateExceptionReport method. GenerateExceptionReport encapsulates writing out whatever sort of information you might want to the report file. Finally, MSJUnhandledExceptionFilter closes the file and chains on to any previously installed UnhandledException-
Inside the GenerateExceptionReport method, the code begins by printing a banner and some basic information about the exception. First out of the gate is the exception number, along with some descriptive text that identifies the exception. Getting this text turned out to be an interesting diversion. If you look at the end of the GetExceptionString method, you'll see that the descriptive strings for each exception that appear in the system's fault dialog are kept in the MessageTable resource of NTDLL.DLL. Unfortunately, I wasn't able to get the FormatMessage API to always give me the raw, unformatted strings, despite my passing the FORMAT_MESSAGE_IGNORE_INSERTS flag. Thus, for common exceptions, the GetExceptionString method returns the #define name from WINBASE.H (for example, "ACCESS_VIOLATION"). If the exception isn't a common one, my code uses FormatMessage on NTDLL's MessageTable and hopes for the best.
After the GenerateExceptionReport method emits the exception number and description, the next step is to print out the faulting address. I first print the linear address of the exception, which usually isn't that helpful, but it's what you'd see in a debugger or the system fault dialog. Following the linear address on the line is the logical address. The logical address consists of the name of the EXE or DLL that the linear address falls within, and the section number and offset within that section. For example, the logical address
means 0x99 bytes into section 1 of MSJTESTEXC.EXE. Given a .MAP file for the faulting module (in this case, MSJTESTEXC.MAP), you could look up the address 01:00000099. Now, you usually won't find an exact address match in a .MAP filewhat you're after is the symbol with a logical address that's the closest to your logical address, while still being less than or equal to it. The fault is probably somewhere within that function.
You may be wondering what sort of voodoo you need to derive a logical address given just the linear address of the exception. It's really not hard if you understand some basics about the structure of Win32 portable executable files. The MSJExceptionHandler::GetLogicalAddress method shows how to convert a linear address to its logical form in less than 40 lines of code. The trick is to use the VirtualQuery function to figure out which module the linear address belongs to. Once the module is known, the code finds the module's section table in memory and walks through the table, looking for the section that encompasses the linear address. We'll see the GetLogicalAddress method again later in this column.
After printing out the type and address of the exception, the GenerateExceptionReport method shows the register values at the time of the exception. It gets this information from a CONTEXT structure. The address of a CONTEXT structure is passed as part of the PEXCEPTION_POINTERS argument to the unhandled exception callback. Currently, my code prints out only the registers for Intel CPUs, but it wouldn't be hard to add code for other CPUs (conditionally compiled, of course).
The last job of the GenerateExceptionReport method is to emit a call stack, which it does by invoking the IntelStackWalk function. As with the register values, I wrote code only for the Intel CPU. (Next month I'll show you how IMAGEHLP.DLL provides a portable method of walking the stack.) After the stack walk, the GenerateExceptionReport method is done. If you want to extend my code to print out additional information, such as the value of program variables, this is a good place to do so. Remember, my code is just a framework; I hope you'll find it a useful starting point for your own exception reporting needs.
I'll finish up this month by describing stack frames and walking the stack in Intel CPU code. In the general case of 32-bit code, the call stack for Intel CPUs is very straightforward. Think of each stack frame as a node in a linked
list. After finding the head of the list, you simply follow the "next" pointer that's a part of each frame. Each frame lies somewhere within the thread's stack region, and each successive node must be at a higher linear address in memory. This is a natural by-product of the way stack frames are generated.
If you've looked at the assembly code that compilers generate for the beginning of a function, you've probably seen these instructions:
Since this instruction sequence is usually at the very beginning of the function's code, it's not hard to picture what the stack looks like at this point. When the CPU called the function, it pushed a return address onto the stack. Then, the instructions above put the current EBP value right below the return address on the stack, before setting EBP to the same value as the stack pointer (the ESP register). The net effect is that afterwards, the EBP value is a pointer to 2 DWORDS on the stack that look like this:
DWORD ;; EBP+4 = ReturnAddress
DWORD ;; EBP+0 = Previous EBP value
That's it! At the bare minimum, a stack frame consists of those two DWORDS. Generally speaking, the EBP register points to the current function's stack frame. This is a key point, so go back and reread the sentence and ponder the implications. The first DWORD of the current function's stack frame is really a pointer to the stack frame for the calling function. If you then examine the first DWORD of that frame, you have a pointer to the stack frame for the calling function. As I mentioned earlier, the call stack is really nothing more than a linked list of these stack frames.|
Walking the list of stack frames isn't immediately useful in itself. However, each frame you encounter also happens to have a code address in it. Now we're getting somewhere! You can use these return addresses to figure out what function in your code they correspond to. A very minimal call stack display would simply walk the call stack and write out the return address that it finds in each node.
Next, you need to know how to start the stack walk. Remember the CONTEXT structure that I mentioned earlier? In the context structure, you'll find the instruction pointer (EIP) at the time of the exception. The first address in a stack walk is traditionally the instruction pointer. Subsequent addresses are obtained from walking the stack frames. How do you find the first stack frame? Easy! Get the EBP register value, which is also in the CONTEXT structure, and use it as a pointer to the first frame.
The last bit of advice I'll give you on stack walking this month is to know when to stop walking. There are no hard and fast rules here. Most stack-walking code stops when
it encounters a frame address that doesn't look valid. There are several tests that you can do to determine if a frame address is valid. For starters, each frame has to be at a higher address than the preceding frame. Another condition is that the frame address has to be a multiple of four bytes, because the stack pointer is always a multiple of
four (assuming that a bug or error hasn't crept in somewhere). Yet another frame validity test is that you need to be able to read and write memory at the frame's address. There are other tests you could apply, but the two I mentioned are usually sufficient to terminate the stack walk at the right time.
My code that implements the algorithm described above is in the IntelStackWalk method (see Figure 1). For each frame, the code prints the code address, the associated stack frame's address, and the logical address of the code. To obtain the logical address for each frame, I used the GetLogicalAddress method described earlier. Armed with just a .MAP file, you should be able to look up the logical addresses to get a stack walk that has the names of your functions. Of course, this is just the sort of busy work that computers are good for. Next month, I'll extend MSJExceptionHandler to use IMAGEHLP.DLL functions to create stack walks with symbolic function names.
At this point, it's necessary to interject a word of warning: compilers don't always emit stack frames. As you can guess, this makes walking the stack essentially impossible in this situation without outside help. Compilers usually will include stack frames when compiling without optimizations. Unoptimized code is typically created when building in debug mode (as opposed to release mode). If you build your program in release mode, you probably won't have stack frames, and the stack-walking code won't get very far. However, you can force the compiler to emit stack frames regardless of the optimization setting; in Visual C++®, the command-line option is /Oy-. Next month, I'll show how IMAGEHLP.DLL is able to walk the call stack of code compiled with a Microsoft compiler, even when there aren't any stack frames present.
To wrap up this month, let me explain a couple of limitations of the MSJExceptionHandler code. First, if an exception occurs before the class constructor is called, the unhandled exception filter won't be set up yet. This is most likely to happen if you have other static classes with constructors. Since it can be messy to predict the order the constructors will be invoked, it's possible that another class's constructor could blow up before MSJExceptionHandler has initialized. Another related limitation involves DLLs. Because Windows invokes the DllMain functions of all implicitly loaded DLLs before it begins execution in the executable, one of the DllMains may blow up. Again, MSJExceptionHandler won't be initialized yet so you won't get a report. Despite these limitations, I think you'll find the framework I've created to be a useful addition to your bag of diagnostic tricks. More next month!
Have a question about programming in Windows? Send it to Matt at firstname.lastname@example.org
© 1997 Microsoft Corporation. All rights reserved. Legal Notices.