Module 0 Exercises — Toolchains, Targets, and the Availability Matrix
Back to the Course 2 syllabus. Read first: Module 0 lessons (Seacord 1, Rust Book 1 and 14, and the Embedded Rust Book’s Getting Started remain available as optional deep-dives).
Work in the labs repo’s course2/ folder — c/host, c/mcu, rust/host, rust/qemu, rust/mcu, rust/linux as each exercise names — and record everything in m0/notes.md. Everything in this module runs on the Mac: host builds with clang and cargo, Cortex-M cross-compiles with clang --target=thumbv7em-none-eabihf and cargo --target thumbv7em-none-eabihf, bare-metal execution in QEMU. Flashing the NUCLEO with probe-rs and building on the Jetson are the optional last steps of Exercises 0.1 and 0.4 only. Predicted cells are filled in before building; observed cells at the machine.
Exercises
Exercise 0.1 — Bring-up on every tier. Install the toolchain from lessons §3 and prove each reach exists with the smallest possible artifact. C: build c/host (one trivial executable under ASan/UBSan), then cross-compile a freestanding kernel in c/mcu and disassemble it. Rust: cargo build the workspace for the host, cargo check -p mcu --target thumbv7em-none-eabihf, cargo check -p linux --target aarch64-unknown-linux-gnu, and cargo build -p qemu --target thumbv7m-none-eabi. Fill the reach table:
| Reach | Command that proves it | Toolchain component it needs | Works? |
|---|---|---|---|
| Host C with sanitizers | … | … | … |
| Cortex-M C, compile + disassemble | … | … | … |
| Cortex-M C, link a real ELF | … | arm-none-eabi-gcc (optional) |
… |
| Host Rust | … | … | … |
thumbv7em-none-eabihf check |
… | … | … |
thumbv7m-none-eabi build |
… | … | … |
aarch64-unknown-linux-gnu check |
… | … | … |
| NUCLEO flash (optional) | probe-rs info / cargo run -p mcu --bin … |
… | … |
| Jetson build (optional) | ssh … cargo build --release |
… | … |
Deliverable: the table plus the version line of every tool involved (clang --version, rustc -Vv, qemu-system-arm --version, probe-rs --version), recorded in notes.md so that a future toolchain drift has a baseline to diff against.
Exercise 0.2 — Hello, no_std, in QEMU. Write rust/qemu/src/bin/ex-0-2.rs: a #![no_std] #![no_main] program with cortex-m-rt’s #[entry], panic-halt, one hprintln!, and debug::exit. Run it with cargo run. Then, one change at a time, and predicting the outcome before each: (a) remove the use panic_halt as _; line; (b) change fn main() -> ! to fn main(); (c) replace the final loop {} with a return-free fall-off; (d) call hprintln! before #[entry] is reached by moving it into a static initializer expression. Record for each whether the failure is a compile error, a link error, a QEMU hang, or a runtime trap, and which tool reported it.
| Change | Predicted failure (stage, message) | Observed |
|---|---|---|
| (a) no panic handler | … | … |
(b) returning main |
… | … |
| (c) fall off the end | … | … |
| (d) I/O before entry | … | … |
Deliverable: the working program, the table, and one paragraph in notes.md on what cortex-m-rt did between reset and your first line.
Exercise 0.3 — Startup, side by side. Read cortex-m-rt’s Reset handler (the crate source is in ~/.cargo/registry) next to the Course 3 labs repo’s startup_stm32l476xx.s and its linker script. Map every responsibility of lessons §5.2 to the exact lines in each, then confirm two of the claims with tools: use cargo objdump -- -d on the QEMU binary to find the vector table and the .data-copy loop, and cargo size -- -A to read the sizes of .vector_table, .text, .rodata, .data, and .bss. Predict, before running cargo size, the ordering of those five sections by size for the hello program and whether .data is empty.
| Responsibility | cortex-m-rt (file:line) |
startup_stm32l476xx.s / .ld (file:line) |
Verified how |
|---|---|---|---|
| Initial stack pointer | … | … | … |
| Vector table placement | … | … | … |
.bss zeroing |
… | … | … |
.data copy from flash |
… | … | … |
| Default exception handler | … | … | … |
| Jump to user entry | … | … | … |
Deliverable: the table and the annotated objdump excerpt showing the vector table’s first two words.
Exercise 0.4 — The availability matrix, by experiment. Lessons §8 makes claims; this exercise tests them. For each row, write the smallest probe that would fail to compile or link if the facility were absent, in both languages, and build it for the bare-metal tier (c/mcu freestanding; rust/mcu with cargo check --target thumbv7em-none-eabihf) and the host. Suggested probes: #include <threads.h> / use std::thread; _Atomic uint64_t with atomic_is_lock_free / core::sync::atomic::AtomicU64; a double multiply compiled at -O2 with the disassembly inspected for a library call vs. an FPU instruction / the same in Rust with f64; fopen / std::fs::File; clock_gettime / std::time::Instant; signal / nix::sys::signal. Record the exact diagnostic for every absent facility.
| Facility | Bare metal C — predicted / observed | Bare metal Rust — predicted / observed | Host (either) |
|---|---|---|---|
| Threads | … / … | … / … | … |
| 8-byte atomic, lock-free | … / … | … / … | … |
double in hardware |
… / … | … / … | … |
| Files | … / … | … / … | … |
| Monotonic clock (without hardware setup) | … / … | … / … | … |
| Signals | … / … | … / … | … |
Optional rung: repeat the Rust column on the Jetson (cargo build on the board) to fill the Linux tier from a real aarch64 glibc rather than from the Mac.
Deliverable: the completed matrix and, in notes.md, which cells surprised you and which the lessons page got wrong, if any.
Exercise 0.5 — Deliberate build failures, read closely. Produce and explain six failures, three per language, choosing constructs that fail at different stages: C — a hosted header in a freestanding build, a long-width assumption pinned by _Static_assert that holds on the Mac and breaks on thumbv7em, and a -Wconversion diagnostic promoted to an error with -Werror; Rust — a std item in a #![no_std] crate, a #[global_allocator]-less Vec, and a Clippy unwrap_used denial in the mcu crate. For each, record the stage (preprocess, compile, link, lint), the first line of the diagnostic, and the one-line fix that keeps the intent.
| Failure | Stage | First diagnostic line | Fix |
|---|---|---|---|
| … | … | … | … |
Deliverable: the table. The point is fluency in the messages: by Module 7, “the compiler said” should be a precise quotation.
Exercise 0.6 — The warning set, applied. Take one real C file from the Course 3 labs repo firmware (a HAL-generated main.c is fine) and one Rust file you wrote in this module. Build the C file under the lessons §7.1 warning set on clang and, if installed, arm-none-eabi-gcc; run cargo clippy --all-targets -- -D warnings on the Rust crate. Triage every diagnostic into fix, justify in a comment, or the warning is wrong here, and why. Count them by flag.
| Flag / lint | Count | Fixed | Justified | Disputed |
|---|---|---|---|---|
| … | … | … | … | … |
Deliverable: the table and the resulting pinned warning-set lines committed to c/host, c/mcu, and rust/ — the contract every later module builds under.
Exercise 0.7 — The Python environment and the device probe. From the labs-repo root: uv sync, then uv sync --group ml, then uv run python course2/python/src/smoke.py and uv run pytest course2/python/tests. Record the resolved versions of numpy, scipy, matplotlib, pandas, scikit-learn, torch, onnxruntime from uv pip list (or uv tree). Then write python/src/ex-0-7.py: print numpy.__config__.show()’s BLAS/LAPACK backend, numpy.finfo(numpy.float32) and finfo(float64), the result of torch.backends.mps.is_available() / torch.cuda.is_available(), and — the first cross-language probe — the strides, flags['C_CONTIGUOUS'], and nbytes of a (4, 3) int16 array before and after .T, alongside the byte count the equivalent C int16_t[4][3] occupies. Predict every value before running.
| Probe | Predicted | Observed |
|---|---|---|
| BLAS backend NumPy links against on the Mac | … | … |
float32 machine epsilon / float64 machine epsilon |
… | … |
| Accelerator visible to PyTorch on the Mac | … | … |
(4, 3) int16 strides, C order |
… | … |
Same array after .T: strides, contiguous? |
… | … |
nbytes vs. sizeof(int16_t[4][3]) |
… | … |
Optional rung: repeat the accelerator row on the Jetson (torch.cuda.is_available(), and CuPy’s cupy.cuda.runtime.getDeviceProperties(0)) and on the RTX 4090 desktop over SSH.
Deliverable: the table, the version list in m0/notes.md, and the pytest run green — the environment Module 1 starts from.