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
- 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. - 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!
Disable page sliding
Rating Riot is a tool for automatic vote exchange between users of social networks.
EM is a swedish furniture manufacturer.
Habo Gruppen manufactures handles and other household items.
On Wineshare you can hire a winestock and purchase your own personal wine.