|Copyright © Microsoft Corporation. This document is an archived reproduction of a version originally published by Microsoft. It may have slight formatting modifications for consistency and to improve readability.|
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.
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 file—what 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
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:
PUSH EBP MOV EBP,ESP
|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
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
|From the April 1997 issue of Microsoft Systems Journal.|