# Gem #125: Detecting infinite recursion with GDB's Python API

Let's get started...

Suppose that you are printing a table of factorials from 9 down to 0, as follows:

```with Ada.Text_IO; use Ada.Text_IO;
procedure Try is
function Factorial (I : Integer) return Integer is
begin
if I = 0 then
return 1;
else
return I * Factorial (I - 1);
end if;
end Factorial;
N : Integer := 9;
F : Integer;
begin
loop
F := Factorial (N);
Put (Integer'Image (N));
Put ("! = ");
Put (Integer'Image (F));
New_Line;
exit when N < 0;
N := N - 1;
end loop;
end Try;
```

Now build the example:

```gnatmake -g try.adb
```

When you execute this program, it will either crash with Storage_Error, or SIGSEGV, or hang, or corrupt the memory, depending on the target platform:

```\$ ./try
9! =  362880
8! =  40320
7! =  5040
6! =  720
5! =  120
4! =  24
3! =  6
2! =  2
1! =  1
0! =  1
raised STORAGE_ERROR : EXCEPTION_STACK_OVERFLOW
```

If you run the program in GDB, by using 'catch exception' you can detect infinite recursion in procedure Factorial:

```(gdb) catch exception
(gdb) run
Starting program: C:\home\guitton\GIT\GDB\builds\gems\1    ry.exe
9! =  362880
8! =  40320
7! =  5040
6! =  720
5! =  120
4! =  24
3! =  6
2! =  2
1! =  1
0! =  1
Program received signal SIGSEGV, Segmentation fault.
0x00401797 in try.factorial (i=-698787) at try.adb:9
9                return I * factorial (I - 1);
(gdb) backtrace 10
#0  0x00401797 in try.factorial (i=-698787) at try.adb:9
#1  0x0040179c in try.factorial (i=-698786) at try.adb:9
#2  0x0040179c in try.factorial (i=-698785) at try.adb:9
#3  0x0040179c in try.factorial (i=-698784) at try.adb:9
#4  0x0040179c in try.factorial (i=-698783) at try.adb:9
#5  0x0040179c in try.factorial (i=-698782) at try.adb:9
#6  0x0040179c in try.factorial (i=-698781) at try.adb:9
#7  0x0040179c in try.factorial (i=-698780) at try.adb:9
#8  0x0040179c in try.factorial (i=-698779) at try.adb:9
#9  0x0040179c in try.factorial (i=-698778) at try.adb:9
(More stack frames follow...)
```

But now you would like to know the context of the call to Factorial that causes the recursion. This can't easily be done at the exception point, as the debugger has too many frames (698787, presumably) to unwind to get to the original call.

If you add a breakpoint in Factorial, you would do several continue commands without finding anything suspicious:

```(gdb) b factorial
Breakpoint 1 at 0x40177f: file try.adb, line 6.
(gdb) run
Starting program: C:\home\guitton\GIT\GDB\builds\gems\1    ry.exe
Breakpoint 1, try.factorial (i=9) at try.adb:6
6             if I = 0 then
(gdb) cont
Continuing.
Breakpoint 1, try.factorial (i=8) at try.adb:6
6             if I = 0 then
(gdb) cont
Continuing.
Breakpoint 1, try.factorial (i=7) at try.adb:6
6             if I = 0 then
```

There are unfortunately a large number of valid calls to Factorial before the bogus one. What we would like to do is set an upper bound on the possible calls to Factorial, and stop only when that bound is reached.

Fortunately, the Python API of GDB can help: with this API, you can go through the debugger structures and get more information about the context at a point of execution. For example, here is a Python function that counts the number of frames in a backtrace:

```def frame_count():
"""Count the number of frames in the backtrace"""
c = 0
while f is not None:
c = c + 1
f = f.older()
return c
```

The function uses the class Frame from the Python API of GDB, and its method older()/newer() to browse through the backtrace.

After creating a file frame_count.py that contains the implementation of this command, it can then be added to the commands of GDB and used as follows:

```(gdb) source frame_count.py
(gdb) python help(frame_count)
Help on function frame_count in module __main__:
frame_count()
Count the number of frames in the backtrace
(gdb) python print frame_count()
4
```

Now you can set a breakpoint in Factorial that will stop only when frame_count reaches a maximum bound. Edit frame_count.py to add the following breakpoint hook:

```def factorial_hook():
"""Called whenever Try.Factorial is reached"""
max_number_of_frames = 100
fc = frame_count()
if fc <= max_number_of_frames:
gdb.execute("continue")
else:
print ""
print ("warning: more than %d frames in backtrace."
% max_number_of_frames)
print "         To ease the investigation, the selected frame will be"
print "         the first call to factorial."
print ""
while f is not None and f.name() == "try.factorial":
gdb.Frame.select(f)
f = f.older()
# Finally, show the frame that this loop selected; it is the first
# call to factorial
gdb.execute("frame")
```

Then attach this command to a breakpoint in Try.Factorial:

```(gdb) source frame_count.py
(gdb) break factorial
Breakpoint 1 at 0x40177f: file try.adb, line 6.
(gdb) commands
>python factorial_hook()
>end
```

You can then run, and the program will stop when the maximum bound is reached. At that point you'll be able to investigate the infinite recursion much more easily:

```(gdb) run
Breakpoint 1, try.factorial (i=-100) at try.adb:6
6             if I = 0 then
warning: more than 100 frames in backtrace.
To ease the investigation, the selected frame will be
the first call to factorial.
#99 0x0040179c in try.factorial (i=-1) at try.adb:9
9                return I * Factorial (I - 1);
```

We can now see that Try calls Factorial with -1; there is a bug in the exit condition in the loop.

```(gdb) up
#100 0x004017c1 in try () at try.adb:17
17            F := Factorial (N);
(gdb) print N
\$2 = -1
```

This is just one of the advanced debugging capabilities that the Python API of GDB provides. For more information, have a look at the corresponding section in the GDB user's guide.

infinite_recursion.zip