![]() |
Chapter 6 | ![]() |
It's sad when you realize how few people actually know how to use the gdb debugger, or the Microsoft equivalent. It is such a t ime saver, and compared to the effort and time required to learn the language itself, picking up skill with gdb is a trivial cost. Both MSVC's debugger and GDB are trivial to use, and this section will cover those points.
Debuggers can be simple or complex, but they all allow some manner of viewing the values of individual data points throughout the life of a program, halting the execution of a program so it can be examined, and usually some stack frame manipulation tools.
Before we go any further, let's discuss what a program 'looks' like in memory, and how that's represented by debuggers. Lets start with a simple hello world program:
int main(int argc,char *argv[]) { printf("Hello World\n"); return 0; }Your interface with this data is fairly intuitive; variables are defined by their variable name (such as argc, or argv[0]). Expressions are evaluated exactly as they would be in C; array or pointer dereferencing, addresses, etc. So, you can use your debugger to show you the value of argc, or the address of argc with little effort.
There is a trick though, and it comes back to one of those fundamental lessons you learned back when you first picked up the language: scope. We already know that not only are you allowed to use the same variable name in different functions, but that this is the norm - especially for temporary counters and other 'discardable' variables. How do you differenate between one 'i' in a function, and another 'i' in another function?
This is where we introduce the concept of a "stack frame". A stack frame is, simply, all the information in the current scope that has been saved in such a way that it can be restored. For our sample program up above, a simplified stack might flow like this:
Event | Stack Frame | |||
Run Program | 1 | |||
Save Argc, Argv. | 1 | |||
Run Printf Function | 2 | |||
Display Text![]() | 2 | |||
End Printf Function | 2 | |||
Restore Argc, Argv | 1 | |||
Exit Program | 1 |
This too is pretty simple, but as you know, most programs are more than just two function calls deep. As a matter of fact, most of the work a programmer does in CircleMUD is usually somewhere in the range of 6 to 9 functions deep (of course, many exceptions exist). The scope of the variables that you're watching, etc, is all based on this stack frame level, and all debuggers should have a mechanism to switch between the different stack frame levels. Our example above has only 2 frames, one lets me see the variables and other data relevant to the 'main' function, the other refers to the 'printf' function.
As you progress through the execution of your program, these frames are popped off when they're not needed. You can see this above as well, as the stack frame depth goes from 1, to 2, back to 1 again. That's why this list of frames is called a stack: A stack is a list that has a last-in first-out order. It may help to envision it as a stack of trays, as in a cafeteria. Only the topmost tray is available, you can either grab it, or put another one on top of it; in which case that one is the new top stack.
Of course, a debugger can examine any frame in a stack without having to worry about order, or popping off the previous stacks.
One last thing of note that confuses many beginning programmers is the preprocessor. A preprocessor is the tool which allows us to use commands like '#include' or '#define'. These keywords are not part of the language, and CircleMUD uses them rather intensively as a way to make aliases to what would otherwise be a long command, like
#define MOB_FLAGS(ch) ((ch)->char_specials.saved.act)
This lets us type "MOB_FLAGS(ch)" instead of the much longer "ch->char_specials.saved.act". Often times (and personally I'm against this), defines are used to emulate functions or functionality. Defines are used in this way to perform generic list operations in CircleMUD, which is one of the few times I can agree that it simplifies the situation instead of making it worse.
Here's the problem: ALL debuggers operate on the code the compiler receives, which is code that has already passed through the preprocessor. Thus, trying to look at 'MOB_FLAGS(ch)' inside a debugger is meaningless, as the preprocessor has replaced any instances of "MOB_FLAGS(ch)" with the actual string "ch->char_specials.saved.act". Not only that, but the debugger has no idea what MOB_FLAGS(ch) is - you'll have to type the whole thing out every time you want to see the act flags. Generally speaking, the same features of the preprocessor which make it easier to abstract or shorten common commands are the same features which make a program harder to debug. Remember this later as it will come up quite often regardless of the debugger you use!
![]() |
Index | ![]() |
5.4 Closing notes about logs. | 6.1 Before You Start Using Gdb |