$ clang -g -O0 13_null_funcptr.c -o bin/13_null_funcptr
$ ./bin/13_null_funcptr ; echo "exit=$?"
calling through a null function pointer...
Segmentation fault (core dumped)
exit=139$ clang -g -O0 13_null_funcptr.c -o bin/13_null_funcptr
$ ./bin/13_null_funcptr ; echo "exit=$?"
calling through a null function pointer...
Segmentation fault (core dumped)
exit=139$ gdb -q -batch -ex 'set debuginfod enabled off' -ex run -ex bt \
-ex 'x/i $pc' -ex 'info registers rip' -ex 'frame 1' \
-ex 'print f' ./bin/13_null_funcptr
Program received signal SIGSEGV, Segmentation fault.
0x0000000000000000 in ?? ()
=> 0x0: Cannot access memory at address 0x0
#0 0x0000000000000000 in ?? ()
#1 0x000000000040049b in main () at 13_null_funcptr.c:83
rip 0x0 0x0
#1 0x000000000040049b in main () at 13_null_funcptr.c:83
83 f(); // <-- THE BUG
$1 = (fn_t) 0x0
How to read each command:
bt -> frame #0 is "0x0 in ?? ()": there is NO function at
address 0, so gdb has no symbol — that "??" is the tell
that you jumped through a bad pointer. Frame #1 is YOUR
code: main at line 83, the call site.
x/i $pc -> "Cannot access memory at address 0x0": you can't even
disassemble the current instruction — $pc points at
unmapped memory. Proof the fault is a bad code fetch.
info reg rip -> rip == 0x0: the program counter itself is null.
frame 1 -> SWITCH to main's frame. You must do this before
inspecting main's locals: in frame #0 (??) there is no
debug info, so `print f` fails with "No symbol f in
current context". After `frame 1` it works:
print f -> $1 = (fn_t) 0x0 — the null function pointer, confirmed.$ clang -g -O0 -fsanitize=address 13_null_funcptr.c -o bin/13_null_funcptr.asan
$ ./bin/13_null_funcptr.asan
AddressSanitizer:DEADLYSIGNAL
==NNNNN==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000
==NNNNN==The signal is caused by a READ memory access. (the instruction fetch)
ASan confirms a SEGV at address 0; gdb is what tells you it was a CALL
(rip=0, frame #0 = "??") rather than a data access. valgrind is not needed
here (it's a plain SIGSEGV, no heap involved).
ROOT CAUSE vs CRASH SITE:
The crash SITE is address 0 (frame #0, "??") — not in your source at all.
The ROOT CAUSE is the call `f()` with f==NULL (frame #1, line 83). With
indirect calls, ALWAYS read frame #1: frame #0 is just "wherever the bad
pointer sent us". A null/garbage `rip` + a "??" frame #0 == jumped through
a bad function pointer.segfault (bad control transfer)
2 ------------------------------------------------------------
`f` is a function pointer set to NULL, then CALLED: `f();`. The CPU tries to jump to address 0 and execute — but address 0 is unmapped and not executable, so it faults. Buggy line: `f();`. (Unlike a data NULL deref, the fault is at the INSTRUCTION FETCH: $pc itself becomes 0.)
"Segmentation fault (core dumped)", exit 139. Deterministic.
Never call an unverified function pointer: if (f) f(); // guard it and initialize callbacks to a real function (or a safe default), not NULL.
Set `f = (fn_t)0xdeadbeef;` instead. Re-run under gdb: now frame #0 is "0xdeadbeef in ?? ()" and rip==0xdeadbeef — same shape, different bad address. The pattern (??/garbage rip) is how you recognize the bug class. ============================================================
#include <stdio.h>
typedef void (*fn_t)(void);
int main(void) {
fn_t f = NULL; /* null function pointer */
printf("calling through a null function pointer...\n");
f(); /* <-- THE BUG: call via NULL */
return 0;
}A lab is done when you can say what it showed on your machine — not when you have read it.
It decides which problems come first, which drills open, which labs you see and how the Learn surface is organised. Change it any time from the switch in the top bar, or press Escape to look around first.