(Appendix) Debugging with jdb
Table of Contents
Introduction
A lot of beginner programmers do what's called "print debugging"—using print statements to debug their programs. Although it tends to be fine for tiny programs, it is often not enough to get at the heart of catastrophic errors in complicated programs. This is where the use of programs specifically made for debugging shines.
jdb
jdb is the OpenJDK debugger which was made to pair with javac in order to debug Java programs. It's important to compile java files with -g if you want to use the debugger.
Below is an example Java program which has a simple bug in it:
1: public class AverageCalculator { 2: public static double average(int[] arr){ 3: int sum = 0; 4: for(int i = 0; i < arr.length; i++){ 5: sum += arr[i]; 6: } 7: return sum / arr.length; // bug: integer division truncates result 8: } 9: 10: public static void main(String[] args){ 11: int[] grades = {85, 92, 78, 91, 88}; 12: double avg = average(grades); 13: System.out.printf("Average: %.2f%n", avg); // prints 86.00, should be 86.80 14: } 15: }
Compiling and running it I get:
josephraskind@stargazer:/tmp/jdb$ java AverageCalculator.java Average: 86.00
Which is clearly not the 86.80 I would have expected. This tells me I should fire up the debugger. I can do so with a call to jdb ./AverageCalculator:
josephraskind@stargazer:/tmp/jdb$ jdb AverageCalculator Initializing jdb ... >
I've now entered jdb. There are a handful of core jdb instructions that are often always helpful:
run- Execute the program
stopstop at mbreaks at the beginning of themmethod
next- Advances one line at a time (bypasses methods)
step- Advances one line at a time (enters methods)
locals- Prints out local variables
jdb let's you freeze a program and look at it step by step:
> stop at AverageCalculator.average > Deferring breakpoint AverageCalculator.average. It will be set after the class is loaded. > run > run AverageCalculator Set uncaught java.lang.Throwable Set deferred uncaught java.lang.Throwable > VM Started: Set deferred breakpoint AverageCalculator.average Breakpoint hit: "thread=main", AverageCalculator.average(), line=3 bci=0 3 int sum = 0; main[1]
Here I set a breakpoint at the entrance to the method average and run the program. Once program execution hits average it is paused. I can then step one line (after the method arguments are initialized) and look at the local variables and arguments:
main[1] step
main[1] >
Step completed: "thread=main", AverageCalculator.average(), line=4 bci=2
4 for(int i = 0; i < arr.length; i++){
main[1] locals
main[1] Method arguments:
arr = instance of int[5] (id=451)
Local Variables:
sum = 0
We see that jdb is now paused at the for statement. I use locals to check the value of all local variables (here sum is initialized to 0).
I can then keep using next to go from one line to the next. Eventually, we'll be able to get to the average calculation:
main[1] next main[1] > Step completed: "thread=main", AverageCalculator.average(), line=7 bci=22 7 return sum / arr.length; // bug: integer division truncates result main[1] print sum / arr.length main[1] sum / arr.length = 86
I can use the print function to evaluate expressions using the available symbols at that point in the runtime. We can see that the integer division is causing this error. Now it is a simple process of exiting jdb with quit and fixing the code!
Using jdb can get extremely complicated. For this class, most major issues can be solved with setting a breakpoint and stepping through a function line by line while examining local variables/global data structures. It is a great resource and should not be ignored when you are in trouble.