Debugging and development tooling for C
You find bugs in C programs systematically with gdb, Valgrind, sanitizers, strace and static analysis, and set up tidy projects with CMake and tests.
Level 301 · 21 lessons · 21 practice labs
Lessons
Build and project tools
- Advanced make: pattern rules and dependency generation
- Setting up a project with CMake
- Building static and shared libraries
- Compiler flags: -O, -g, -fsanitize and hardening
- Short task: build a library and its test with CMake
Debugging with gdb
- gdb basics: breakpoints, stepping, printing
- Backtraces and frames: bt, frame, info locals
- Watchpoints: who changed this value?
- Core dumps: ulimit and post-mortem analysis
- gdb TUI and command files
- Short task: find why the program crashes
Memory and behaviour analysis
- Reading Valgrind memcheck output
- AddressSanitizer and UndefinedBehaviorSanitizer
- Undefined behaviour: why did the compiler delete your code?
- System and library calls with strace and ltrace
- Short task: the bug that only appears with -O2
Static analysis and testing
- Static analysis: cppcheck and clang-tidy
- Unit testing in C: a tiny test harness
- Measuring coverage: gcov and lcov
- Short task: zero warnings, zero analyser findings
Capstone
- Capstone: find and document four bugs in a legacy C tool
Practice labs
- A library and a program with CMake: Define a helper library and a program using it as CMake targets and run the test with ctest.
- Rebuilding when a header changes: Add -MMD -MP dependency generation to a Makefile that fails to rebuild objects when a header changes.
- Giving a shared library a soname: Build a library as shared with a soname and version, link the program with an rpath and verify it with ldd.
- Tracing a wrong average with gdb: Find and fix the faulty line in a program that computes an average using breakpoints and stepping.
- Reading the backtrace of a crashing program: Use bt and frame in a parser that segfaults to find where the NULL pointer comes from.
- Finding what corrupts a variable with a watchpoint: Catch the code that corrupts a counter at an unexpected moment with a watchpoint and fix the root cause.
- Finding a crash cause from a core dump: Open the core dump of a job that crashed overnight in gdb, inspect the variables at the crash and fix the bug.
- Resolving a Valgrind invalid read report: Find the line in Valgrind's 'Invalid read of size 4' report and fix the read past the end of the array.
- Finding a heap overflow with ASan: Build the program with -fsanitize=address, interpret the report and fix the one-byte heap overflow and the second overflow that only appears with a long input.
- Catching an undefined shift with UBSan: Fix the signed overflow and oversized shift that UBSan reports so the tests pass in both builds.
- Solving a bug seen only with -O2: Find the undefined behaviour in code that works at -O0 but loops forever at -O2 and make it standard-conforming.
- Fixing type punning with memcpy: Rewrite code that reads float bits through a pointer cast using memcpy and show the results stay the same.
- Finding an uninitialised variable: Find the uninitialised variable in a calculation whose result changes with build settings, using warnings and Valgrind.
- Finding a missing file with strace: Use strace to find which file a silently failing tool looks for and improve its error message.
- Resolving cppcheck findings: Fix the resource leak and bounds findings cppcheck reports in a small project and bring the count to zero.
- A tiny macro-based test harness: Write a test harness with an ASSERT_EQ macro and test registration that reports failures with file and line.
- Finding uncovered branches with gcov: Raise line coverage above ninety percent by testing the uncovered error branches in the gcov report.
- Cleaning up clang-tidy warnings: Scan an old module with selected clang-tidy checks and fix the unsafe calls and readability issues.
- Closing the paths the compiler analyser finds: Build the program with gcc -fanalyzer, follow the reported null-dereference and leak paths step by step, fix them and get a clean analysis.
- Testing allocation failure paths: Replace malloc calls with a counting wrapper that makes a chosen call fail, and show with tests and a coverage report that every error path returns without leaking.
- Listing a program's system calls: Run a small program under strace, read the files it opens and its failed calls from the summary table, and write the most repeated call to the report file.
About this course
You find bugs in C programs systematically with gdb, Valgrind, sanitizers, strace and static analysis, and set up tidy projects with CMake and tests.
What you will learn
- Build and project tools
- Debugging with gdb
- Memory and behaviour analysis
- Static analysis and testing
- Capstone
Prerequisites
You should be comfortable with the level 101 and 201 topics of the same path.
Lab environment
Lessons run in your own Kali Linux terminal that opens in the browser. Nothing to install; the lab starts clean in every lesson.
Frequently asked questions
- Is this course right for my level?
- The course is at level 301; it assumes you know the topics of the lower-level courses.
- Is there anything to install?
- No. Every lesson runs in your own lab environment that opens in the browser.
- How do lessons work?
- Each lesson has 8 to 10 steps and a short quiz. Early steps show the command to type, later steps give less help; if two attempts fail, the solution opens.
- Can I try it for free?
- Yes. The first three lessons of every course are free.