$ clang -g -O0 01_null_deref.c -o bin/01_null_deref
$ ./bin/01_null_deref ; echo "exit=$?"
about to read through a null pointer...
Segmentation fault (core dumped)
exit=139
(We never reach "x = ..." — it died on the read.)$ clang -g -O0 01_null_deref.c -o bin/01_null_deref
$ ./bin/01_null_deref ; echo "exit=$?"
about to read through a null pointer...
Segmentation fault (core dumped)
exit=139
(We never reach "x = ..." — it died on the read.)$ gdb -q -batch -ex 'set debuginfod enabled off' \
-ex run -ex bt -ex 'print p' -ex 'info locals' \
./bin/01_null_deref
Program received signal SIGSEGV, Segmentation fault.
0x000000000040049c in main () at 01_null_deref.c:104
104 int x = *p; // <-- THE BUG
#0 0x000000000040049c in main () at 01_null_deref.c:104
$1 = (int *) 0x0
p = 0x0
x = 0
How to read each command:
run -> runs the program inside gdb; it stops the instant the
signal is delivered, ON the faulting instruction.
bt -> backtrace (call stack). Here just frame #0 = main, and
it names the exact file:line (01_null_deref.c:104).
print p -> shows p == 0x0. The smoking gun: we read *through* 0.
info locals -> all locals in the frame; confirms p=0x0 (x not set yet).
(`set debuginfod enabled off` suppresses gdb's interactive prompt asking
whether to download missing debuginfo from the Fedora server — see
README "debuginfod".)$ valgrind -q ./bin/01_null_deref
==46685== Invalid read of size 4
==46685== at 0x40049C: main (01_null_deref.c:104)
==46685== Address 0x0 is not stack'd, malloc'd or (recently) free'd
==46685== Process terminating with default action of signal 11 (SIGSEGV)
==46685== Access not within mapped region at address 0x0
How to read it:
"Invalid read of size 4" -> a 4-byte (int) load that touched memory
it should not have.
"Address 0x0 is not ..." -> 0x0 isn't stack/heap/recently-freed: it is
simply unmapped. => NULL deref.
ASan variant (faster; also dumps registers):$ clang -g -O0 -fsanitize=address 01_null_deref.c -o bin/01_null_deref.asan
$ ./bin/01_null_deref.asan
==46704==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000
==46704==The signal is caused by a READ memory access.
==46704==Hint: address points to the zero page.
#0 0x4e9ea1 in main .../01_null_deref.c:104:13
"Hint: address points to the zero page" == NULL (or near-NULL) deref.$ clang -g -O0 -S -emit-llvm 01_null_deref.c -o 01.ll
$ opt -passes=verify 01.ll -disable-output # -> "IR verified OK"
Relevant IR inside main:
store ptr null, ptr %2, align 8 ; p = NULL
%5 = load ptr, ptr %2, align 8 ; tmp = p (== null)
%6 = load i32, ptr %5, align 4 ; *** = *p <-- FAULTS HERE
`%6 = load i32, ptr %5` is the dereference: load 4 bytes from the pointer
we just loaded (null). opt -passes=verify reports the IR is *well formed*
— verification checks STRUCTURE, not that a pointer is valid at runtime.
(META-LESSON: a verifier is not a bug finder.)
ROOT CAUSE vs CRASH SITE:
Here they nearly coincide, but the crash SITE is the load (`*p`) while the
root CAUSE is the earlier assignment `int *p = NULL;`. The load merely
exposes the bad VALUE set earlier. In real code the NULL usually comes
from a function that returned NULL (malloc/fopen/lookup) far from the
deref — walk the backtrace and `print` the pointer to find WHERE it
became NULL.segfault
1 ------------------------------------------------------------
`p` is set to NULL (`int *p = NULL;`) and then dereferenced (`int x = *p;`). Reading the int at virtual address 0 is forbidden — the kernel never maps page 0 — so the CPU raises a fault. (Buggy line: `int x = *p;`.)
"Segmentation fault (core dumped)", shell exit code 139 (139 = 128 + signal 11 = 128 + SIGSEGV). Deterministic: address 0 is *never* mapped, so it crashes every run.
Give p real storage, or guard the deref: int v = 42; int *p = &v; // point at a real object or if (p) { int x = *p; ... } // never deref without a check
Change `int x = *p;` to `*p = 5;` (a WRITE). Re-run under gdb/valgrind: it now says "Invalid write" / "WRITE memory access". Same root cause, different access kind. ============================================================
#include <stdio.h>
int main(void) {
int *p = NULL; /* root cause: p points at address 0 */
printf("about to read through a null pointer...\n");
*p=5;
int x = *p; /* <-- THE BUG: dereference of NULL */
printf("x = %d\n", x);
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.