FForge
← Labsdebug-11Runtime debugging●●○○

Infinite recursion / stack overflow

debug/11_infinite_recursion.c3 steps
0/3 steps
evidence to leave with
gdb giant backtrace; bt -5 shows the base; ASan stack-overflow
walk it, one step at a time
run this
$ clang -g -O0 11_infinite_recursion.c -o bin/11_infinite_recursion
$ ./bin/11_infinite_recursion ; echo "exit=$?"
what it printed on the machine that wrote this lab
Segmentation fault (core dumped)
exit=139
run this
$ gdb -q -batch -ex 'set debuginfod enabled off' -ex run \
what it printed on the machine that wrote this lab
      -ex 'bt 4' -ex 'echo ...\n' ./bin/11_infinite_recursion
Program received signal SIGSEGV, Segmentation fault.
0x0000000000400481 in recurse (depth=261945) at 11_infinite_recursion.c:81
#0  0x0000000000400481 in recurse (depth=261945) at 11_infinite_recursion.c:81
#1  0x0000000000400486 in recurse (depth=261944) at 11_infinite_recursion.c:81
#2  0x0000000000400486 in recurse (depth=261943) at 11_infinite_recursion.c:81
#3  0x0000000000400486 in recurse (depth=261942) at 11_infinite_recursion.c:81
(More stack frames follow... a quarter-million of them.)
How to read it:
  - The SAME function repeats forever in the backtrace -> runaway
    recursion. That repetition IS the diagnosis.
  - The `depth=` values count DOWN as you go up the stack (261945, 261944,
    261943...), so the deepest live call had depth ~261945: that's how many
    frames deep it got before exhausting the 8 MiB stack. (Representative —
    yours WILL differ; sometimes frame #0's arg even reads as "<error
    reading variable: Cannot access memory>" once SP passes the guard.)
  - The crash SITE is just wherever the stack happened to run out — its
    address/depth is meaningless. The repeating frame is the real signal.
  - `bt 4` limits the dump; a full `bt` here prints ~261945 frames. Use
    `bt N` (innermost N) or `bt -N` (outermost N) on deep stacks so gdb
    doesn't drown you.

ASan reports it crisply as a stack-overflow:
run this
$ clang -g -O0 -fsanitize=address 11_infinite_recursion.c -o bin/11_infinite_recursion.asan
$ ./bin/11_infinite_recursion.asan
what it printed on the machine that wrote this lab
==NNNNN==ERROR: AddressSanitizer: stack-overflow on address 0x.... ...
    #0 ... in recurse .../11_infinite_recursion.c:81
run this
$ valgrind -q ./bin/11_infinite_recursion
what it printed on the machine that wrote this lab
  ==NNNNN== Stack overflow in thread #1: can't grow stack to 0x1ffe801000
  (Then a SIGSEGV with a very deep stack trace. memcheck CAN flag stack
   exhaustion, but the giant gdb backtrace is the fastest way to SEE that a
   single function is eating the stack.)

ROOT CAUSE vs CRASH SITE:
  The crash SITE is an arbitrary deep frame where the stack finally ran out
  — its exact address/depth is meaningless and changes every run. The ROOT
  CAUSE is structural: recurse() has no terminating condition. Don't debug
  the crash address; recognize the repeating frame and fix the recursion.
category

stack error (stack exhaustion) / segfault

difficulty

3 ------------------------------------------------------------

what's wrong

`recurse` calls itself with no base case, so it never returns. Each call pushes a new stack frame; after ~millions of frames the stack (8 MiB by default) is exhausted and the next push hits the guard page -> SIGSEGV. Buggy line: `return recurse(depth + 1) + 1;` (missing a stop condition).

symptom

"Segmentation fault (core dumped)", exit 139. Deterministic as a crash, but the DEPTH reached varies (ASLR/frame size). NOTE: the `+ 1` defeats tail-call optimization, so even -O2 would recurse for real here; at -O0 every call uses a frame.

the fix

Give the recursion a base case that is always reached: int recurse(int depth) { if (depth >= LIMIT) return 0; // base case return recurse(depth + 1) + 1; } Or rewrite as an explicit loop with bounded iterations.

take it further

Add `if (depth > 100000) return 0;`. It now returns normally. Then lower the stack limit with `ulimit -s 1024` (1 MiB) and watch the overflow happen at a SHALLOWER depth — proof the limit is the stack size, not the code. ============================================================

read the source · debug/11_infinite_recursion.c
static int recurse(int depth) { return recurse(depth + 1) + 1; /* <-- THE BUG: no base case ever stops it */ } int main(void) { return recurse(0); }
◇
Finish the lab: write the evidence note

A lab is done when you can say what it showed on your machine — not when you have read it.