When debugging your application, you use breakpoints. The program will return control to GDB every time it reaches a breakpoint you set. This may not be desirable if you have breakpoint on a method that is called many times and you want to break only with certain values passed to that method. GDB provides several ways to do conditional breakpoints that I’ll try to explain.
- Easily preview Mermaid diagrams
- Live update when editing
- Capture screenshots
- Create PNG from the Terminal
- Free download on the Mac App Store
1- The problem
Let take a really simple application which calls 10 times a function.
#import <Foundation/Foundation.h>
void myFunction()
{
// Initialize the counter to 0
static int sFunctionCounter = 0;
// Each time we enter the function,
// increment the counter
sFunctionCounter++;
NSLog(@"function called: %d", sFunctionCounter);
}
int main (int argc, const char * argv[])
{
NSAutoreleasePool * pool =[[NSAutoreleasePool alloc] init];
int i;
// Call 10 times myFunction.
for(i = 0 ; i < 10 ; i++)
myFunction();
[pool release];
return 0;
}
Now imagine that you want to break in myFunction, but only the 4th time you enter that function. One easy solution would be to modify the function by adding a if statement and setting a breakpoint accordingly.
This method works fine in that case but you need to modify your function, you need to rebuild, you can’t do it if you don’t have the sources…
2- Conditional breakpoints
GDB can let you add breakpoints to stop whenever a certain point in the program is reached. The problem is that if you set a breakpoint to the function myFunction, you will break 10 times and will need to continue until you get the right occurrence.
Hopefully you can specify a condition (boolean expression) on your breakpoint so that your program stops only if the condition is true.
Let’s take the sample of the previously detailed program.
We will use the Console in Xcode but you can use directly GDB in command line if you prefer. Here is what to do:
- First we set a breakpoint at the entry of the main function of the program.
- Set a breakpoint to stop on
myFunction
: break myFunction. - Finally tell the debugger to only break if sFunctionCounter is equal to 4: condition 9 sFunctionCounter == 4
The program will run and will stop at the 4th call of the function myFunction as you can see on this screenshot:
3- Breakpoints commands
Conditional breakpoints are useful but not really flexible. GDB provides another solution: the breakpoint commands.
You can give any breakpoint a series of commands to execute when your program stops due to that breakpoint. For example, you can display the values of certain expressions or enable/disable some breakpoints.
We will take another simple example. Our new program call 10 times 2 methods with detachNewThreadSelector
. The calls to method1:
and method2:
are asynchronous so that we can’t tell if method1:
will be executed before method2:
.
#import <Foundation/Foundation.h>
@interface Test : NSObject
{
}
-(void)method1:(id)sender;
-(void)method2:(id)sender;
@end
@implementation Test
-(void)method1:(id)sender
{
// Do something
}
-(void)method2:(id)sender
{
// Do something
}
@end
int main (int argc, const char * argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
int i;
Test *myTest = [[Test alloc] init];
// Call 10 times the methods method1: and method2:.
// The calls are asynchronous so that we can't tell if method1:
// will be executed before method2:.
for(i = 0 ; i < 10 ; i++)
{
NSLog(@"iteration: %d", i);
// Call method1: asynchronously
[NSThread detachNewThreadSelector:@selector(method1:) toTarget:myTest withObject:nil];
// Call method2: asynchronously
[NSThread detachNewThreadSelector:@selector(method2:) toTarget:myTest withObject:nil];
// Wait before continuing to loop
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
}
[myTest release];
[pool release];
return 0;
}
What we would like to do, is to break in method2: only if method2: is called before method1:.
This is not trivial to do with conditional breakpoints, and not easy to do by modifying the source code.
Let’s talk bout the powerful breakpoint commands, which can solve this. You will need to write a little txt file. This file contains the commands to execute after a breakpoint is reached.
# Define two integers which will be incremented
# each time you enter the corresponding method.
set $method1_Counter = 0
set $method2_Counter = 0
# We create a breakpoint using "future break"
# If we break due to this breakpoint, the command
#following will be executed.
fb method1:
command
set $method1_Counter = $method1_Counter + 1
printf "method1 calledn"
continue
end
# We create the second breakpoint and
# the coressponding command.
fb method2:
command
set $method2_Counter = $method2_Counter + 1
printf "method2 calledn"
if($method1_Counter < $method2_Counter)
printf "method2 was called before method1!n"
else
continue
end
end
As you can see, this little txt file is really simple. We define 2 breakpoints, one in method1:
and one in method2:
. For each breakpoint defined, we define a command (kind of a small program) which will be executed.
The command related to the breakpoint method1: prints method1 called
and we tell the debugger to continue the execution of the program.
The command related to the breakpoint method2:
prints method2 called
and we only tell GDB to continue the program if method1_Counter
< method2_Counter
(the number of calls to method2:
is greater than the number of calls to method1:
). If method1:
was called before method2:
, we continue the execution of the program.
Now that we have our txt file, we can start our application, load the txt file using the source command and see that indeed we only break if method2:
is called before method1: