Knowing how to instruct the debugger where or when to pause execution of your program is important, but knowing how to instruct it to resume execution is just as important. After all, inspecting your variables may not be enough. Sometimes you need to know how the variables' values interact with the rest of the code.
Recall the Principle of Confirmation in Chapter 1: You continue to confirm that certain variables have the values you think they do, until you encounter one that fails to match your expectation. That failure will then be a clue as to the likely location of your bug. But typically the failure will not occur until you have paused and resumed execution at a number of breakpoints (or multiple times at the same breakpoint). Thus, resuming execution at a breakpoint is just as important as setting the breakpoint itself, which is why a debugging tool will typically have a fairly rich set of methods for resuming execution.
There are three classes of methods for resuming execution. The first involves "single stepping" through your program with step
and next
, executing only the next line of code and then pausing again. The second consists of using continue
, which makes GDB unconditionally resume execution of the program until it hits another breakpoint or the program finishes. The last class of methods involves conditions: resuming with the finish
or Until
commands. In this case, GDB will resume execution and the program will run until either some predetermined condition is met (e.g., the end of a function is reached), another breakpoint is reached, or the program finishes.
We'll consider each method of resuming execution in turn for GDB, and then show how to perform such operations in DDD and Eclipse.
We'll start this section by discussing the various ways you can resume execution once GDB is paused at a breakpoint.
Once GDB stops at a breakpoint, the next
(abbreviated as n
) and step
(abbreviated as s
) commands are used to single-step through your code. After a breakpoint is triggered and GDB pauses, you can use next
and step
to execute just the very next line of code. After the line is executed, GDB will again pause and give a command prompt. Let's take a look at this in action. Consider the program swapflaw.c:
Example Listing 2-1. swapflaw.c
1 /* swapflaw.c: A flawed function that swaps two integers. */ 2 #include <stdio.h> 3 void swap(int a, int b); 4 5 int main(void) 6 { 7 int i = 4; 8 int j = 6; 9 10 printf("i: %d, j: %d\n", i, j); 11 swap(i, j); 12 printf("i: %d, j: %d\n", i, j); 13 14 return 0; 15 } 16 17 void swap(int a, int b) 18 { 19 int c = a; 20 a = b; 21 b = c; 22 }
We'll set a breakpoint at the entry to main()
and run the program in GDB.
$ gcc -g3 -Wall -Wextra -o swapflaw swapflaw.c $ gdb swapflaw (gdb) break main Breakpoint 1 at 0x80483f6: file swapflaw.c, line 7. (gdb) run Starting program: swapflaw Breakpoint 1, main () at swapflaw.c:7 7 int i = 4;
GDB is now at line 7 of the program, meaning that line 7 has not been executed yet. We can use the next
command to execute just this line of code, leaving us just before line 8:
(gdb) next 8 int j = 6;
We'll use step
to execute the next line of code, line 8, which moves us to line 10.
(gdb) step 10 printf("i: %d, j: %d\n", i, j);
We see that both next
and step
execute the next line of code. So the big question is: "How are these commands different?" They both appear to execute the next line of code. The difference between these two commands is how they handle function calls: next
will execute the function, without pausing within it, and then pause at the first statement following the call. step
, on the other hand, will pause at the first statement within the function.
A call to swap()
is coming up at line 11. Let's look at the effect of next
and Step
side by side.
Using step
:
(gdb) step i: 4, j: 6 11 swap(i, j); (gdb) step swap (a=4, b=6) at swapflaw.c:19 19 int c = a; (gdb) step 20 a = b; (gdb) step 21 b = c; (gdb) step 22 } (gdb) step main () at swapflaw.c:12 12 printf("i: %d, j: %d\n", i, j); (gdb) step i: 4, j: 6 14 return 0;
Using next
:
(gdb) next i: 4, j: 6 11 swap(i, j); (gdb) next 12 printf("i: %d, j: %d\n", i, j); (gdb) next i: 4, j: 6 14 return 0;
The step
command works the way you might expect it to. It executed the printf()
on line 10, then the the call to swap()
on line 11,[6] and then it began executing lines of code within swap()
. This is called stepping into a function. Once we step
through all the lines of swap, step
brings us back to main()
.
In contrast, it appears that next
never left main()
. This is the main difference between the two commands. next
considers the function call to be a single line of code, and executes the entire function in one operation, which is called stepping over the function.
However, don't be fooled; it may look like next
skipped over the body of swap()
, but it didn't really step "over" anything. GDB silently executed each line of swap()
without showing us the details (although it shows any screen output that swap()
may print) and without prompting us to execute individual lines from the function.
The difference between stepping into a function (what step
does) and stepping over a function (what next
does) is such an important concept that, at the risk of belaboring the point, we'll demonstrate the difference between next
and step
with a diagram that shows the program execution using arrows.
Figure 2-8 illustrates the behavior of the step
command. Imagine that the program is paused at the first printf()
statement. The figure shows where each step
statement will take us:
Figure 2-9 illustrates the same thing, but using next
instead.
Whether you use next
or step
is really a matter of what you are trying to do. If you're in a part of the code with no function calls, it doesn't matter which one you use. In this case, the two commands are completely equivalent.
However, if you're debugging a program and find yourself about to step into a function that you know is free of bugs (or irrelevent to the bug you're trying to track down), clearly you'd want to use next
to save yourself from stepping through each line of a function you're not interested in.
One of the general debugging principles laid out in Chapter 1 was to take a top-down approach to debugging. If you're stepping through source code and encounter a function call, it is typically better to use next
instead of Step
. Immediately after using next
in this situation, you would check if the result of the call was correct. If so, the bug is likely not in the function, meaning the use of next
instead of step
saved you the time and effort of stepping through every line of the function. On the other hand, if the result of the function is incorrect, you can re-run the program with a temporary breakpoint set at the function call, and then use step
to enter the function.
Both the next
and step
commands take an optional numerical argument which indicates the number of extra lines to next or step through. In other words, next 3
is the same as typing next
three times in a row (or typing next
once followed by hitting the ENTER key twice).[7] Figure 2-10 shows an illustration of what next 3
does:
The second method of resuming execution is with the continue
command, abbreviated as c
. In contrast to step
and next
, which execute only one line of code, this command causes GDB to resume execution of your program until a breakpoint is triggered or the program terminates.
The continue
command can take an optional integer argument, n
. This number tells GDB to ignore the next n
breakpoints. For instance, continue 3
tells GDB to resume program execution and ignore the next 3 breakpoints.
Once a breakpoint is triggered, the next
and step
commands are used to execute the program line by line. Sometimes this can be a painful endeavor. For example, suppose GDB reached a breakpoint within a function. You've inspected a few variables and have gathered all the information you had intended to get. At this point, you're not interested in single-stepping through the remainder of the function. You'd like to return back to the calling function where GDB was before you stepped into the called function. However, setting an extraneous breakpoint and using continue
seems wasteful if all you want to do is skip the remainder of the function. That's where finish
comes in.
The finish
command (abbreviated fin
) instructs GDB to resume execution until just after the current stack frame finishes. In English, this means that if you're in a function other than main()
, the finish
command will cause GDB to resume execution until just after the function returns. Figure 2-11 illustrates the use of finish
.
Although you could type next 3
instead of finish
, it is easier to type the latter, which involves counting lines (anything more than a half dozen would be a needless nuisance).
It may not look like finish
executes each line of code as it takes you to the bottom of the function, but it does. GDB executes each line without pausing[8] except to show the program's output.
Another common use of finish
is when you've accidentally stepped into a function that you meant to step over (in other words, you used step
when you meant to use next
). In this case, using finish
places you right back where you would've been had you used next
.
If you're within a recursive function, finish
will only take you one level up in the recursion. This is because each call is considered a function call in its own right, since each one has its own stack frame. If you want to get completely out of a recursive function when the recursive level is high, a temporary breakpoint along with Continue
, or using the until
command, is more appropriate. We will discuss until
next.
Recall that the finish
command completes execution of the current function without further pauses within the function (except at any intervening breakpoints). Similarly, the Until
command (abbreviated simply as u
) is typically used to complete an executing loop, without further pauses within the loop, except at any intervening breakpoints within the loop. Consider the following code snippet.
...previous code
... int i = 9999; while (i--) { printf("i is %d\n", i); ...lots of code
... } ...future code
...
Suppose GDB is stopped at a breakpoint at the while
statement, you've inspected a few variables, and now you'd like to leave the loop to debug "future code."
The problem is that i
is so large, it would take forever to use next
to complete the loop. You can't use finish
because that command will pass right over "future code" and take us out of the function. You could set a temporary breakpoint at the future code and use continue;
however, this is exactly the situation that until
was meant to address.
Using until
will execute the rest of the loop, leaving GDB paused at the first line of code following the loop. Figure 2-12 shows an illustration of what using until
will do:
Of course if GDB encounters a breakpoint before leaving the loop, it will still pause there: If there were a breakpoint at the printf()
statement in Figure2-12, you'd certainly want to disable it.
The GDB User's Guide gives the official definition of until
as:
Execute until the program reaches a source line greater than the current [one].
However, the documentation also warns that this can be a little counterintuitive. To demonstrate why, consider the following program:
Example Listing 2-2. until-anomaly.c
#include <stdio.h> int main(void) { int i; for (i=0; i<10; ++i) printf("hello world!"); return 0; }
We'll set a breakpoint at the entry of main()
, run the program, and use until
to reach the return
statement.
$ gdb until-anomaly Using host libthread_db library "/lib/tls/libthread_db.so.1". (gdb) break main Breakpoint 1 at 0x80483b4: file until-anomaly.c, line 7. (gdb) run Starting program: until-anomaly Breakpoint 1, main () at until-anomaly.c:7 7 for (i=0; i<10; ++i) (gdb) until 8 printf("hello world!"); (gdb) until 7 for (i=0; i<10; ++i) (gdb) until 10 return 0; (gdb)
Whoa! Using until
, GDB went from line 7 to line 8 and then back to line 7. Surely this doesn't mean that source line 7 is greater than source line 8? Actually, it does. Perhaps you can guess the answer by now, since it seems to be a common theme. GDB ultimately works with machine instructions. Although the for
construct is written with the loop test at the top of the body, GCC compiled the program with the conditional at the bottom of the loop body. Since the conditional is associated with line 7 of the source code, it appears that GDB went backwards in the source code. In fact, what until
really does is execute until it reaches a machine instruction that has a higher memory address than the current one, rather than until it reaches a larger line number in the source code.
In practice, this sort of thing may not arise too often, but it's nice to understand it when it does occur. Additionally, by looking at some of GDB's odd behavior, you can glean information about how compilers turn source code into machine instructions—not a bad bit of knowledge to have.
If you are curious and you know the assembly language of your machine, you can take a quick look at the machine code, using GDB's disassemble
command, followed by p/x $pc
to print out the current location.[9] This will show you what Until
will do. But it's just a quirk, and in practical terms, it is not an issue. If you are at the end of a loop and issuing the until
command causes a jump back to the top of the loop, simply issue until
a second time, and you will leave the loop as desired.
The until
command can also take an argument of a location within your source code. In fact, it takes the same arguments as the break
command that was discussed in Setting Breakpoints in GDB. Returning to Example Listing 2-1, if GDB triggered a breakpoint on the entry of main()
, these are all equivalent ways of conveniently executing the program until the the entry to swap()
:
until 17
until swap
until swapflaw.c:17
until swapflaw.c:swap
We'll start this section by discussing the various ways you can resume execution once DDD is paused at a breakpoint.
DDD has buttons for both next
and step
in the Command Tool. In addition, you can perform a step
and next
with the F5 and F6 function keys, respectively.
If you want to use next
or step
with an argument in DDD, that is, accomplish what you would do in GDB via
(gdb) next 3
you'll need to use GDB itself in DDD's Console Window.
DDD has a button for continue
in the Command Tool, but again, if you want to execute continue
with an argument, use the GDB Console. You can left-click the Source Window to bring up a "continue until here" option, which really sets a temporary breakpoint (see Setting Breakpoints in GDB) at that line of source code. More accurately, "continue until here" means "continue until this point, but also stop at any intervening breakpoints."
In GDB, one might achieve the same effect as finish
via next
with a numerical argument, so that finish
would be only marginally more convenient. But in DDD, using finish
is a clear win, requiring a single mouse click in the Command Tool.
If you're using DDD, you can perform an until
by using the button labeled Until on the Command Tool, or left-click the Program | Until menu bar, or use the keyboard shortcut F7. Of all those options, you're bound to find one of them convenient! As with many other GDB commands, if you want to use until
with an argument, you'll need to give them directly to GDB in the DDD Console Window.
As noted in Undoing/Redoing Breakpoint Actions in DDD, DDD has an invaluable Undo/Redo feature. In that section, we showed how to undo an accidental breakpoint deletion. It can also be used on actions such as Run, Next, Step, and so on.
Consider, for example, the situation depicted in Figure 2-13. We had reached the breakpoint at the call to swap()
and had intended to do a Step operation, but accidentally clicked Next. But by clicking Undo, you can roll back time, as shown in Figure 2-14. DDD reminds you that you have undone something by displaying its current-line cursor as an outline, instead of the solid green it normally uses.
Eclipse's analogs of step
and next
are the Step Into and Step Over icons. The Step Into icon is visible in the Debug view (upper left) in Figure 2-15; the mouse pointer has temporarily caused the icon label to appear. The Step Over icon is just to the right of it. Note that the next statement you will execute will be the call to get_args()
, so clicking Step Into would result in the next pause of execution occurring at the first statement within that function, while selecting Step Over would mean the next pause would be at the call to process_data()
.
Eclipse has a Step Return icon (next to Step Over) that performs finish
. It has nothing exactly corresponding to Until
. Its Run to Line (invoked by clicking the target line and then right-clicking in the source code window) will typically accomplish what you would want with until
.
[6] You may wonder why step
did not take you to the first line of the function printf()
. The reason is that GDB does not stop within code for which it does not have debugging information (i.e., the symbol table). The function printf()
, being linked in from the C library, is an example of such code.
[7] Vim users should feel right at home with the concept of specifying a count for a given command.
[8] If there are any intervening breakpoints, finish
will pause at them.
[9] You can also view the machine code by using the -S
option of GCC. This will produce an assembly language file with suffix .s, showing the code that was produced by the compiler. Note that this produces the assembly language file only, not an executable.