# GCC Debugging **Debug symbols** (`-g` flag) embed source file names, line numbers, and variable information into the binary. [[gdb]] uses these to map machine instructions back to source code. ```bash gcc -g -o app app.c # include debug symbols gcc -g -O0 -o app app.c # debug build (no optimization) gcc -g -O3 -o app app.c # optimized with debug symbols (can work) ``` Always use `-g` when compiling for debugging. The flag doesn't increase runtime overhead—it only adds metadata to the binary. You can remove it later with `strip`. **Debug symbols and optimization:** At `-O0`, line numbers and variable locations are precise. At `-O3`, the compiler reorders code and optimizes variables away, so line number attribution becomes approximate. For serious debugging, use `-O0 -g`. For production debugging of optimized code, accept that stepping through source may jump around unexpectedly. **DWARF versions:** `-gdwarf-4` (default on GCC 10+) uses DWARF version 4. Older tools may need `-gdwarf-3`. Unless you have compatibility issues, use the default. **Separate debug symbols:** ```bash gcc -g -o app app.c objcopy --only-keep-debug app app.debug objcopy --strip-debug app app_stripped objcopy --add-gnu-debuglink=app.debug app_stripped ``` This separates debug info from the binary, reducing binary size while keeping debuggability. The `app.debug` file can be stored separately. **Testing with gdb:** ```bash gcc -g -O0 -o app app.c gdb ./app (gdb) break main (gdb) run (gdb) next (gdb) print variable_name ``` With debug symbols, `gdb` shows function names, file names, line numbers, and can print variable values. Without `-g`, you only get memory addresses and registers. **Core dumps:** When a program crashes, the OS can save a core dump (memory snapshot). Debug with it using gdb: ```bash ulimit -c unlimited # enable core dumps ./app # program crashes, produces core gdb ./app ./core # debug the core dump (gdb) bt # backtrace: where did it crash? (gdb) print variable_at_crash ``` Core dumps let you debug crashes that happened earlier, without re-running the program. **Address Sanitizer:** Detect memory errors at runtime (similar to [[valgrind]] but faster): ```bash gcc -fsanitize=address -g -O1 -o app app.c ./app # reports memory errors: use-after-free, buffer overflow, etc. ``` `-fsanitize=address` adds instrumentation to catch memory bugs. It's slower than native execution but faster than Valgrind. `-O1` is recommended (optimizations make debugging harder, but `-O0` adds more overhead).