Labs, Projects and Assessments

Reading builds understanding; doing builds skill.

Learning Objectives

Apply every module in twenty guided labs.

Build a capstone project at your level and assess it against a rubric.

Check your mastery with module tests and a final assessment.

Plain-Language Explanation

Reading builds understanding; doing builds skill. Each lab below is a short, focused exercise with a goal, steps, the result you should see, and a solution sketch. The capstones are larger projects you can put in a portfolio. Every lab can be done with the toolchains listed in the Course Overview; labs marked *Linux* need a Linux system or WSL.

Precise Technical Explanation

You should observe Convert decimal addresses to hexadecimal Convert 4096, 65535, 3,221,225,472 and 140,737,488,355,327 to hex

0x1000, 0xFFFF, 0xC0000000, 0x7FFFFFFFFFFF (top of the x86-64 user range) Decode little- and big-endian bytes

Decode 78 56 34 12 and 00 00 0C E4 both ways LE 0x12345678; BE 0x00000CE4 = 3300 (Module 8's serialized reading)

Trace local, static, global and allocated variables Extend m03_layout with a second function and a static local; predict each region before running your predictions match the /proc/self/maps regions

Visualize function calls and stack frames Use the stack visualizer for factorial(4), then print __builtin_frame_address(0) at each depth frame addresses decrease by a constant frame size

Inspect .text, .data and .bss (*Linux*) Add a 1 MiB zero-initialised array and a 1 MiB initialised array; compare size and the file size only the initialised array grows the file Compile through every stage

Reproduce m02_stages on your own program; add -save-temps

.i, .s, .o files and the same symbols Fix a memory leak

Run m06_leak under LeakSanitizer, fix it, rerun the report disappears Fix a use-after-free in a safe sandbox

Run m09_uaf under ASan; restructure ownership so the read happens before free Resize a dynamic array safely

Implement push with overflow-checked doubling and a temporary for realloc; test with forced failures (a wrapper that returns NULL on the n-th call) data remains valid and freeable after a failed grow Analyse structure padding

Use offsetof/sizeof on three structs of your design; reorder to minimise predictions match; size reduced Compare row- and column-major traversal

Run m13_locality at 512×512, 2048×2048 and 8192×8192 the gap grows once the matrix exceeds the caches Inspect process memory maps (*Linux*)

cat /proc/self/maps, pmap -x $$, smaps_rollup for a running program

RSS < VSZ; shared libraries counted in RSS Read a debugger call stack

Run m05_backtrace; then reproduce it in GDB with break level3 + bt the same four frames Run sanitizer-assisted tests

Build your Lab 9 code with -fsanitize=address,undefined and run its tests Build a ring buffer

Extend m08_ring with peek and size; then make it SPSC-safe with atomics tests pass; TSan clean with one producer and one consumer Create a memory-pool allocator

Extend m06_pool to two size classes with double-free detection O(1) operations; double free reported Trace a thread race and fix it

Run m14_tsan_src under TSan (use setarch -R with GCC 11), fix with a mutex and with an atomic TSan clean; total always 2000 Analyse an embedded linker map

Build m16_fw with -Wl,-Map=fw.map; find each symbol's address and the .data LMA matches readelf output in Module 16 Design an MCU memory budget

Fit the m16_budget plan into 16 KiB RAM with ≥ 10% margin Create a crash triage report

From the m18_workflow ASan report, write a triage report: summary, impact, root cause, fix, regression test a reproducible report like Module 18's E3 Solution sketches for selected labs

Lab 1 — repeated division by 16: 3,221,225,472 = 3 × 2³⁰ = 0xC0000000; 140,737,488,355,327 = 2⁴⁷ − 1 = 0x7FFFFFFFFFFF.

Lab 5 — .bss grows size's *bss* column but not the file; .data grows both.

Lab 9 — a failing realloc must leave the caller's pointer and capacity unchanged; free it exactly once at the end.

Lab 15 — producer writes the slot then atomic_store_explicit(&head, …, release); consumer atomic_load_explicit(&head, acquire) then reads the slot.

Lab 19 — e.g. heap 0x800 → 0x400 and one task stack 0x400 → 0x300 gives 15,104 bytes (92%); to reach ≥ 10% margin also trim .bss buffers or the main stack after measuring their high-water marks.

Memory visualizer showing addresses, bytes and values input of typed values; hex/binary/byte-order views; explanation panel C dynamic-array library

create/push/pop/get/destroy, overflow-safe growth, allocation-failure handling, ownership documentation, tests under ASan/UBSan Memory profiler dashboard an LD_PRELOAD or wrapper-based allocation logger (synthetic/sandboxed workloads), live bytes, peak usage, allocation sites, leak suspects at exit MCU memory-map and linker-map analyser parse a GNU ld map file, per-section and per-symbol sizes, flash/RAM budget warnings, top-N symbols Cross-language memory behavior report reproducible experiments (C, C++, one GC language, Python) on allocation cost, retention and layout, with methodology and caveats

Short concept checks after every lesson (each module's quiz).

Code tracing and predict-the-output questions (the step-by-step traces and exercises).

Multi-answer questions for architecture-dependent details ("select all that apply").

Debugging tasks with safe test cases (Labs 7, 8, 14, 17, 20).

A module test after each module: its three exercises plus its quiz.

The final assessment below, with beginner, intermediate and advanced questions.

Insufficient (0) all features work; edge cases handled main path works fails main path design and memory behavior explained clearly sanitizer-clean, ownership documented, no UB automated, boundary and failure cases happy path only measured with sound methodology platform assumptions stated and isolated

Why It Matters in Real Systems

Employers value demonstrated skill: a sanitizer-clean library with tests, a profiler you built, or a map-file analyser used on real firmware says more than any certificate. The labs reproduce real engineering tasks at small scale.

Key Terms

A short guided exercise with an expected observation A larger project integrating several modules

Criteria and levels used to assess a project A test that keeps a fixed bug fixed

Memory Layout and Execution Flow

flowchart LR
  B[Labs 1-6: foundations] --> I[Labs 7-12: heap, layout, caches]
  I --> A[Labs 13-17: debugging, concurrency]
  A --> E[Labs 18-20: embedded and triage]
  E --> C[Capstone project]

Syntax

No dedicated syntax; this module applies the syntax of the earlier modules. Lab 1 and Lab 2 can be checked with a few lines of Python:

for n in [4096, 65535, 3_221_225_472, 140_737_488_355_327]:
    print(f"{n:>19,} = 0x{n:X}")
for raw in [bytes([0x78, 0x56, 0x34, 0x12]), bytes([0x00, 0x00, 0x0C, 0xE4])]:
    le, be = int.from_bytes(raw, "little"), int.from_bytes(raw, "big")
    print(f"{raw.hex(' ')}: little-endian 0x{le:08X} ({le}), big-endian 0x{be:08X} ({be})")

Complete Runnable Example

4,096 = 0x1000
             65,535 = 0xFFFF
      3,221,225,472 = 0xC0000000
140,737,488,355,327 = 0x7FFFFFFFFFFF
78 56 34 12: little-endian 0x12345678 (305419896), big-endian 0x78563412 (2018915346)
00 00 0c e4: little-endian 0xE40C0000 (3825991680), big-endian 0x00000CE4 (3300)

Line-by-Line Explanation

f"{n:X}" formats an integer in upper-case hexadecimal; , adds thousands separators.

int.from_bytes(raw, "little") interprets the first byte as least significant; "big" as most significant.

Expected Output

Shown above — compare it with your hand calculations for Labs 1 and 2.

Step-by-Step Execution Trace

For 78 56 34 12 little-endian: 0x78 + 0x56·2⁸ + 0x34·2¹⁶ + 0x12·2²⁴ = 0x12345678.

Interactive Visualization

All ten visualizers are available in their modules: byte viewer (0, 1, 8), compilation pipeline (2, 10, 17), memory map (3, 7), lifetime and ownership (4, 9, 14, 15), stack frames (5, 18), heap allocator (6), CPU (11), address translation (12), cache (13, 19) and embedded memory map (16).

Common Mistakes and Failure Modes

Reading the solution before attempting the lab.

Skipping the "predict first" step — prediction is where learning happens.

Running labs only in release builds, hiding memory errors.

Corrected / Safe Pattern

For every lab: predict → run → compare → explain any difference → run under sanitizers → write down what you learned.

Debugging and Diagnostic Techniques

Use Module 18's tool table; keep a lab notebook with commands, outputs and observations.

Performance Implications

Labs 11 and the advanced capstone require measurement: repeat runs, report medians, state hardware and flags.

Security and Safety Implications

Labs with memory bugs (7, 8, 17, 20) use deliberately small programs run under detection tools. Keep it that way: never point bug-demonstration code at inputs or systems you do not own.

Platform and Language Caveats

Labs 5, 12 and 17 need Linux or WSL; Lab 18 needs a Cortex-M cross toolchain (or the Zig/Clang setup used in Module 16); results involving addresses and timings differ per machine.

Real-World Application

Each capstone mirrors a real tool: memory viewers in debuggers, container libraries, heap profilers (heaptrack), map analysers (Puncover), and cross-language benchmark reports used to choose technologies.

Best Practices Checklist

[ ] Complete at least one lab per module.

[ ] Finish one capstone and assess it against the rubric.

[ ] Pass the final assessment with explanations, not guesses.

Exercises

E1 · Beginner. Complete Labs 1 and 2 by hand, then check with the script.

Answer: See the script output above.

E2 · Intermediate. Complete Lab 9 and show that a forced realloc failure leaves the array usable.

Answer: Wrap realloc in a function that returns NULL on a chosen call number; after the failed push, get still returns the old elements and destroy frees the original block (ASan reports no leak).

E3 · Advanced challenge. Complete the embedded capstone for the map file produced by Lab 18.

Answer: Parse lines of the form section address size file and symbol lines under each section; aggregate per output section and per object file; compare totals with the MEMORY region sizes; warn above a configurable threshold; print the top symbols by size.

Quiz

Final assessment — beginner level:

Final assessment — intermediate level:

Final assessment — advanced level:

? How many bytes does a uint32_t occupy, and how is 0x0A0B0C0D stored on a little-endian machine?
+ 4 bytes: 0D 0C 0B 0A :: Least significant byte first.
- 4 bytes: 0A 0B 0C 0D :: That is big-endian order.
- 8 bytes: 0D 0C 0B 0A 00 00 00 00 :: uint32_t is exactly 32 bits.
- It depends on the compiler. :: The size is fixed; byte order depends on the platform.

? Where does a non-static local variable of a C function normally live?
+ In a register or the function's stack frame :: Automatic storage; the optimizer decides which.
- In .data :: Only initialised static objects go there.
- On the heap :: Only allocated objects live there.
- In .rodata :: Only constants live there.

? What does the linker do?
- Expands macros :: That is the preprocessor.
+ Resolves symbols between object files and libraries and applies relocations :: It produces the executable.
- Executes the program :: The loader and CPU do that.
- Translates C to assembly :: That is the compiler.
? A function returns a pointer to its local array. What is the problem?
+ The array's lifetime ends when the function returns, so the pointer dangles. :: Using it is undefined behavior.
- The array is copied to the heap automatically. :: C does no such thing.
- The pointer is NULL. :: It holds an address, but an invalid one.
- Nothing, if the array is small. :: Size does not matter.

? Which statements about realloc are true? (select all that apply)
+ It may move the block to a new address. :: Pointers into the old block become invalid.
+ On failure it returns NULL and leaves the old block allocated. :: Hence the temporary.
- It always zero-fills the new part. :: The new part is uninitialised.
- It can be used on stack arrays. :: Only on blocks from the malloc family.

? Why can two threads incrementing a shared int lose updates?
+ The increment is a non-atomic read-modify-write that can interleave. :: Use a mutex or an atomic.
- Integers cannot be shared between threads. :: They can, with synchronisation.
- The cache always holds stale values. :: Coherence keeps caches consistent; the problem is atomicity.
- The compiler removes one of the increments. :: The interleaving loses the
…
? A program reserves 8 GiB with mmap on a machine with 4 GiB RAM and touches 100 MiB. What happens on Linux with default settings?
+ It succeeds; only about 100 MiB becomes resident through demand paging. :: Overcommit plus demand paging.
- mmap fails immediately. :: The heuristic overcommit mode allows it.
- The kernel swaps 4 GiB to disk. :: Untouched pages need no memory.
- The OOM killer runs at once. :: Only touched pages need frames.

? Which are needed for correct release/acquire message passing between threads? (select all that apply)
+ The writer stores the data, then performs a release store of the flag. :: Orders the data before the flag.
+ The reader performs an acquire load of the flag before reading the data. :: Orders the flag before the data.
- Declaring the flag volatile is sufficient. :: volatile gives no inter-thread ordering in C/C++.
- A sleep() call between the two stores. :: Timing is not synchronisation.

? On a Cortex-M7 a DMA engine fills a buffer in cacheable RAM. What must software do before the CPU reads it?
+ Invalidate the cache lines covering the buffer (or use a non-cacheable region). :: Otherwise the CPU may read stale cached data.
- Clean the cach
…

Summary

Twenty labs turn every module into practice; five capstones integrate them into portfolio projects assessed by a rubric covering correctness, explanation, safety, tests, performance reasoning and portability. The final assessment checks beginner, intermediate and advanced understanding with explained answers.

References and Further Reading

The exercises and references of every module.

*Computer Systems: A Programmer's Perspective* labs (Data Lab, Bomb Lab, Malloc Lab, Cache Lab) — classic, freely available lab handouts from CMU.

OSTEP homework simulators (paging, caching, scheduling) by the Arpaci-Dusseaus.