The lower part of the dialog lists all the information about the last address looked up. Most of the fields in the lower part of the dialog should be obvious. The Fn Displacement field shows how many code bytes from the start of the function the address is. The Source Displacement field tells you how many code bytes the address is from the start of the closest line. Remember that many assembler instructions can make up a single line, especially if call functions are part of the parameter list.
Keep in mind when using CrashFinder that you cannot look up an address that is not a valid instruction address. If you're programming in C++ and you blow out this pointer, you can cause a crash in an address like 0x00000001. Fortunately, those types of crashes are not as prevalent as the usual memory access violation crashes, which are easily found with CrashFinder.
If your current application is perfect and you do not have any crashes to look up, I included a small sample application in the source code for this month's column that you can use to test out CrashFinder. This application, CrashOmatic, is a simple console executable with two DLLs that can crash in different places. The README file explains how to build it. Now that you know a little about using CrashFinder, I want to point out some of the implementation highlights. Implementing CrashFinder
CrashFinder itself is a straightforward MFC application, so most of it should be familiar. There are three key areas that I want to point out. The first is how I handled the different versions of IMAGEHLP.DLL, the second is where the work gets done in CrashFinder, and the last is the data architecture.
For the IMAGEHLP.DLL symbol engine, I wanted to make sure that I had something that worked no matter which version the user had on their disk. Since I was writing a C++ application, I just encapsulated the whole thing into a class called CSymbolEngine (shown in Figure 3). As I promised in my last column, any reusable code becomes part of the ongoing BugslayerUtil.DLL. All the unit test cases are in the Tests directory under the main BugslayerUtil code directory distributed with this month's source code. While I could have had the CSymbolEngine member functions exported from BugslayerUtil.DLL, I set them up as inline members. This was because exporting C++ classes can be problematic and, in this case, it was not necessary as all the functions are mostly simple wrappers anyway.
Much of the work needed for the CSymbolEngine class was trying to figure out at compile time which header version of IMAGEHLP.H was being used. Since some people might not have the new header, I wanted to make sure that SYMBOLENGINE.H compiled with everyone's code. I use the API_VERSION_NUMBER define to help figure out what should be included. The original SDK has a version number of five, while the updated version is seven. If you compile with the updated version header, I assume that you are also linking to the updated IMAGEHLP.LIB version as well, so CSymbolEngine just becomes a passthrough to the API calls. If you are compiling with the original SDK header, then CSymbolEngine will use GetProcAddress at runtime to determine if the IMAGEHLP.DLL in memory supports the new source and line functions. If you want to use CSymbolEngine without forcing the user to upgrade to the latest IMAGEHLP.DLL, you can define FLEXIBLE_SYMBOLENGINE. The CSymbolEngine class will then use the GetProcAddress method to determine if the source and line functions are present.
I did come across one interesting bug when I developed the CSymbolEngine class. While all the debug builds ran just fine, the release builds would always crash when looking up an address. This stumped me, but when I disassembled CrashFinder, I saw that there was a call through one of the function pointers to one of the new source and line functions that was immediately followed by an ADD ESP,10h instruction. It took me a second to realize that you should only see the stack adjusted after a function call when the function is a cdecl call. Since the IMAGEHLP.DLL functions are all stdcall, I was declaring the functions incorrectly. The problem was in the typedefs for the function pointers; I had forgotten to include the __stdcall keyword.
The second point about CrashFinder's implementation is that all the work is essentially in the document class, CCrashFinderDoc. It holds the CSymbolEngine class, does all the symbol lookup, and controls the view. The key function, CCrashFinderDoc::LoadAndShowImage, is shown in Figure 4. This function is where the binary image is validated and checked against the existing items in the project for load address conflicts, the symbols are loaded, and the image is inserted at the end of the tree. This function is called both when a binary image is added to the project and when opening the project. This way, the core logic for CrashFinder is always in one place and I could have CrashFinder store only the binary image names in the project instead of copies of the symbol table.
My last point is about the data architecture. The main data structure is a simple array of CBinaryImage classes. The CBinaryImage class represents a single binary image added to the whole project and serves up any core information about a single binary—things like load address, binary properties, and name. When a binary image is added, the document adds the CBinaryImage to the main data array and puts the pointer value for it into the tree node extra data slot. When selecting an item in the tree view, the tree view will pass the node back to the document so that the document can get the CBinaryImage and look up its symbol engine information. Exercises for the Reader
Now that you've seen a little bit about how CrashFinder works, let's talk about how you can add some nice functionality. While CrashFinder is a pretty complete application as it stands, I can see some things that would make it much easier to use and more powerful. If you want to learn more about binary images, I would encourage you to add some of the following features. If someone really does add all of this functionality, I will be happy to post the updated CrashFinder so that everyone can benefit.
First, automatically add dependent DLLs. CrashFinder now makes you add each binary image for the project by hand. It would be much nicer if CrashFinder prompted for the EXE and then automatically added all dependent DLLs when creating a new project. While this will not get them loaded dynamically, it saves a lot of individual adding.
Second, show more information in the informational edit control. The CBinaryImage class has the functionality to show more information after the symbol information through the AdditionalInfo method. You could add the ability to show information from the binary image like header information, imported functions, and exported functions.
Third, allow pasting in of DLL lists to automatically add them to the project. The debugger output windows list all the DLLs that are loaded by an application. You could extend CrashFinder to allow the user to paste in text and have CrashFinder scan through the text looking for DLL names.
Finally, fix load address conflicts by rebasing the application. The ReBaseImage API from, where else, IMAGEHLP.DLL allows you to rebase an image yourself. This would make it much more convenient for users instead of forcing them to do it from an MS-DOS prompt. Wrapup
If you follow the simple bugslaying rules I presented and use CrashFinder, you should stand a fighting chance of figuring out where a crash in your application occurred. Now, when you get a crash address from a beta tester, you can be more productive by fixing the bug quicker. The best part is when the screaming VP is in the middle of his rant, you calmly whip out CrashFinder, load up your CrashFinder project, punch in the address, and before he takes another breath you can tell him that you know exactly where it crashed. Da Tips!
Did you hear? MSJ will pay a million dollars for each tip you submit to the Bugslayer column! April Fool's! Anyway, help your fellow developers by submitting a bugslaying tip to me at john@jprobbins.com. Tip 7 Back in the October 1997 Bugslayer column, I wrote a library that helped you avoid memory leaks and other memory problems. Simon Bullen has a freeware library (with source code!) called Fortify that works with all ANSI C and C++ compilers. Fortify is far more complete than the code I presented. It has some excellent features for handling things like strdup, as well as a very nice scheme for checking for corrupted memory.
One of the nicest features is the scope leak checking function. While seeing memory leaked at the end of the application is nice, you almost never know exactly where the leak occurred, only where it was allocated. Fortify has some macros that you can call at the start and end of scope and have it dump any memory leaked between the two. Fortify is worth looking at and can be found at http://www.geocities.com/SiliconValley/Horizon/8596/. Simon mentioned that he is hard at work on the next version and it should be posted by the time you read this. (Thanks to Simon Bullen, sbullen@cybergraphic.com.au.) Tip 8 You know all those Windows structs that have cbSize as the first parameter? If I am going to be using them, I always write my own derived class that initializes the struct. For example: |