"Success is the sum of small efforts, repeated day in and day out." - Robert Collier   |    "If Java had true garbage collection, most programs would delete themselves upon execution." - Robert Sewell   |    "Nothing great was ever achieved without enthusiasm." - Ralph Waldo Emerson   |    Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law.   |    "The more difficulties one has to encounter, within and without, the more significant and the higher in inspiration his life will be" - Horace Bushnell   |    "Perl - The only language that looks the same before and after RSA encryption." - Keith Bostic   |    "Good luck' follows careful preparation; 'bad luck' comes from sloppiness." - Robert Heinlein   |    "Nothing can stop the man with the right mental attitude from achieving his goal; nothing on earth can help the man with the wrong mental attitude." - Thomas Jefferson   |    "The big secret in life is that there is no big secret. Whatever your goal, you can get there if you're willing to work." - Oprah Winfrey   |    "Take calculated risks. That is quite different from being rash." - George S. Patton   |    "We are what we repeatedly do. Excellence, therefore, is not an act but a habit." - Aristotle   |    "The more you learn, the more you need to learn." - Robert Heinlein   |    "Every artist was first an amateur." - Ralph Waldo Emerson   |    "We are still masters of our fate. We are still captains of our souls." - Winston Churchill   |    "The talent of success is nothing more than doing what you can do, well." - Henry W. Longfellow   |    "Our doubts are traitors, and make us lose the good we oft might win, by fearing to attempt." - William Shakespeare   |    "Nothing will ever be attempted if all possible objections must first be overcome." - Samuel Johnson   |    "There is no such thing as luck; there is only adequate or inadequate preparation to cope with a statistical universe." - Robert Heinlein   |   

Code Reactor

The best of modern Web development

Hardcore debugging crashes in C++ release builds in Visual Studio and StackWalker

Ever encountered an error which just shows up in a release build? And when you are debugging everything is just fine? Don’t those errors just make you happy and loving the programming, computers, the world and everything in it? 😛

So, long story short, I had to come up with some reliable debugging methods for the release builds, and there are quite a few.

Internal methods

Well, just to mention, of course, there is logging. However, my error was producing a nice and loud access violation and quitting, so instead of starting to log everything that happens I wanted to start working from that AV. But do not undermine the power

Try/catch

Firs on my list is the good old c++ try/catch blocks. In case of debugging crashes you can use them by just putting the main() or WinMain in one big try/catch, and get the exception object in the catch and start working from there. Based on that object you can deduce the exact error, line, sometimes even the callstack (probably the single most valuable piece of information about a crash). Nothing out of the ordinary, plain and simple.

However, this method will only catch c++ exceptions, not the hardware errors or even things like bad pointers, which is quite a pity because most of bugs lead just to those.

wait, what?- you say, try does not catch all the errors? What is the point then?

Well, basically, there are two kinds of exceptions in C++. First one is

  1. C++ exceptions. These are the errors the code is sort of throwing itself. For example, you download a library for opening image files, and calling a function for opening a jpeg. However it cannot open the file, and uses c++ Throw() to throw an exception, to report that something went wrong and processing of that function has stopeed.
    The control now goes back to your innermost try/catch block and you get your exception there.
    These errors are always thrown by the code itself. It may not be your code, but somewhere there is a Throw() that wants to signal that something went wrong, but it knows about it.
    So these errors are handled in the c++ code, basically by the compiler, and even though they are errors, they are sort of already managed, at least partly. Because somwhere there is a throw, and its purpose is to manage an error.
    These errors a pretty high-level compared to the SEH exceptions, can basically be used as a valid control structure, not only as a fail-safe mechanism. You are free to use try/catch blocks to catch these exceptions through arbitrary calling levels, cycles, and so on. The compiler will automatically free all the memory when it unwinds the call stack to get to your innermost catch block.
  2. SEH exceptions. (and this is windows-specific)
    Now, let’s say you are doing a simple *i = 1;, where i is a pointer that for some reason is pointing to null. This means your program will try to access memory at the very beginning (0) of the address space, and that memory is very reserved, so the operation is invalid.
    But what happens? You have obviously not written any code to Throw() an exception to manage this error. In fact, if you were aware of that this could happen, you could’ve just checked if i is equal to 0, and not assign a value in that case.
    Of course, other scenarios are possible, but bottomline is that sometimes there are operations that produce errors on the assembly level. The bugs (poorly designed code) probably happen before, and lead to these situations, but nevertheless, when this happens, neither the compiler nor the processor know what to do, they cannot analyze the code for you, they just see that something is wrong. Which brings us to

_try/_except

To handle these low-level errors, MS gave us a non-standard extesion to C++, which can be used in visual C++: __try and __except. The OS itself of course has a mechanism for handling these errors, otherwise any poor-written program would be able to cause havoc. There are WINAPI functions for setting SEH exception handlers, getting information about the error and so on. And in Visual C++ we can catch these errors using __try. (If you are on other platforms or other compilers, this section might be of no use to you).

Now, you might ask why isn’t this a standard C++ thing, these are still errors? The usual argumentation is that while C++ exceptions might be thrown by design (instead of constructing a lot of if’s, an author of a problematic function just put a throw() and was done with it), SEH errors often represent serious bugs in the program, and the chance is not big that the program will be able to recover from it, and this is why simply putting a try/catch around a SEH exception and trying to mask it is much more dangerous than doing so with a C++ exception. (For example it could cause a damaged information to be written to disk, instead of just being damaged in a memory)

This is of course, true to some extent, but hey, as long as you know where and how to use it, an error log during debugging is much better than just a binary dump saved by Windows’ default error mechanism.

Basically, if you put a try/catch block around your error, but it doesn’t catch it, try _try/_except. But you will need some other things to make it useful, for example StackWalker, a class to parse all the information from the SEH handler and print it to a nice readable stack trace. (Even with line numbers! And even in release builds! It is very simple to use and all the examples are provided in http://www.codeproject.com/KB/threads/StackWalker.aspx, so I am not going to cite them here.)

Just before we continue, there are some specialties in how VC++ compiles, first which errors are catched by normal try/catch can depend on whether you are building a debug or a release build, this is supposedly a bug in an older version of the compiler. Secondly, there are some compiler switches to make try/catch catch *all* errors in all types of builds (/EH{s|a}[c][-], http://msdn.microsoft.com/en-us/library/1deeycx5(v=vs.71).aspx), but there are a ton of information about this around the google and StackOverflow…

 

Proper IDE-debugging

A very important fact that you may or may not know is that debugging  in an IDE does not actually require the binary to be built in any special way (i.e. debug configuration). The debugger could go into any userlevel program and start stepovering and stepintoing, except in some very special cases. So what is the debug configuration really good for, except slowing down the program you ask? Well I wonder that too sometimes, but basically it adds some extra checks for things that can go wrong (a LOT of checks in STL for example), which can sometimes get you a more comprehend-able error than without those checks. Nevertheless, it is in now way a *requisite* to IDE debugging.

But what is an (almost) requisite are the symbols. The symbols are human-readable markers of things in your binary, like functions and c++ lines. When debugging, it is a lot easier to understand what is going on if the debugger tells you that you are in function suchAndSuch(), on line 342, instead of “You are at location 0x00400F3d”.

Those symbols are saved in a separate file (.pdb), and saving them contra not saving them does not modify the binary itself, so it will run exactly like a binary without those debug symbols, and will most probably crash at the same place (which is what you want to debug).

Long story short, you will need these settings:
1) C/C++ > Debug Information Format: /Zi or /ZI, chances are you won’t have to change this from the default value.
2) Linker > Debugging > Generate Debug Info : YES /DEBUG
3) Linker > Optimization > References: Eliminate Unreferenced Data (/OPT:REF) and Linker > Optimization > Enable COMDAT Folding > Remove Redundant COMDATs (/OPT:ICF). Without going into too much detail about these options, when you set the /DEBUG option, that modifies the default behavior of the linker regarding comdats and references, and by setting these two options you modify those behavior back to the way it is without the /DEBUG option, thus making sure your binary will be the same.

The symbols file will be saved by default in some intermediate directory for your project. While debugging, the IDE will have to find that file.

An interesting fact: by default, Visual Studio stores absolute path to your symbols file in your binary. This is good for debugging on the same machine, because no matter where you start your binary from, if it is debugged in Visual Studio, it will always find the symbols, but if you need to debug on different machines or just think that it is not in any way pro to ship your binaries with absolute paths to things on your development machine in them (like me), you can make it store the relative path with an undocumented command line switch: “/pdbpath:none “. (source: http://groups.google.com/group/microsoft.public.vc.debugger/browse_thread/thread/abbbdead63dfe5ac?pli=1)

 

So that basically should get you started on debugging release builds, for more info, google is always your dearest friend!

Leave a Reply

You must be logged in to post a comment.

loading...
Your connection appears to be too slow, automatically disabling HeavyAjax (TM) for better performance...
You seem to run a browser without JavaScript support or it has been disabled. To fully experience Code Reactor please enable JavaScript. (It is not 1995 anymore :)
You seem to be using Internet Explorer. If you want to experience both Code Reactor and the rest of the web to their fullest and fastest, you are advised to download and install a real browser, like Opera, Firefox or Google Chrome.
Close
You seem not only to use Internet Explorer, which is by far a joke when it comes to browsers, but to even use an old version of it!
If you want to experience both Code Reactor and the rest of the web to their fullest and fastest, you are STRONGLY advised to download and install a real browser, like Opera, Firefox or Google Chrome.
Close