Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,7 @@ jobs:
strategy:
fail-fast: false
matrix:
# Surface drift across LTS kernels. ubuntu-22.04 ships 5.15; 24.04 ships 6.8.
os: [ubuntu-22.04, ubuntu-24.04]
os: [ubuntu-22.04, ubuntu-24.04, ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
Expand Down
5 changes: 0 additions & 5 deletions .vscode/c_cpp_properties.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,5 @@
"${workspaceFolder}/.kernel-cache/current/build/include/linux/compiler_types.h"
]
}
],
"_notes": [
"KBUILD_MODNAME is a placeholder; the actual value at compile time is your module's real name. IntelliSense only needs the macro to resolve, not match exactly.",
"ubuntu/include is Ubuntu-kernel-specific (where the distro's extra headers live); on non-Ubuntu kernels VS Code silently ignores it.",
"All paths under .kernel-cache/current/ resolve only after scripts/02-setup-host-build.sh has run. Before that, expect <linux/...> include squiggles."
]
}
111 changes: 48 additions & 63 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,83 +1,68 @@
# Contributing

Bug reports, fixes, and improvements are welcome. This file describes what
that looks like for this repo.
Bug reports, fixes, and improvements welcome.

## What this repo is (and isn't)
## What belongs here

This is a **template** for building and source-debugging out-of-tree Linux
kernel modules. The lab tooling lives in `Makefile`, `scripts/`,
`.vscode/`, `host/`, and the docs. The example module under
`examples/chuck_norise/` exists to demonstrate the workflow end-to-end.
This is a **template** for building and source-debugging out-of-tree
Linux kernel modules. Lab tooling lives in `Makefile`, `scripts/`,
`.vscode/`, `host/`. The example under `examples/chuck_norise/`
demonstrates the workflow end-to-end.

PRs that **belong** here:
**Yes:**

- Bugs in the lab tooling — broken scripts, wrong assumptions about target
state, scripts that fail on re-run, unclear error messages.
- Support for a new target OS family (currently only Ubuntu). See the
`TARGET_OS` checks in scripts 01 and 02 for where to add a backend.
- New debug methods that fit the `<endpoint> -> GDB` shape (anything
speaking the GDB remote serial protocol).
- Documentation that fixes misleading or missing information.
- Quality-of-life improvements: better error messages, more idempotent
re-runs, faster sync, smaller artifact dirs.
- Bugs in the lab tooling: broken scripts, wrong assumptions, scripts
that fail on re-run, unclear errors.
- New target-OS backends (currently only Ubuntu — see the `TARGET_OS`
checks in scripts 01 and 02).
- New debug methods that fit the `<endpoint> → GDB` shape.
- Docs fixes.
- Quality-of-life: better errors, idempotent re-runs, faster sync.

PRs that **do not** belong here:
**No:**

- Changes to `module/` (that's the user's slot; we keep it as a placeholder
with just a README).
- New example modules under `examples/`. We keep one example focused and
small so it stays useful as a walkthrough.
- Project-management features (work logs, task tracking, etc.). The lab
stays focused on build + debug.
- Changes to `module/` (user's slot; stays as just a placeholder README).
- New examples under `examples/` (one focused example beats many).
- Project-management features (work logs, task tracking).

## Reporting bugs

Open an issue with:

- Your **dev host** (distro, version, kernel) and **target VM** (distro,
version, kernel, hypervisor).
- The debug method you tried (`kgdb` or `qemu`) and the endpoint you
pointed it at.
- The command you ran and the full output. For deploy failures, attach
- Dev host (distro, version, kernel) and target VM (distro, version,
kernel, hypervisor).
- Debug method tried (`kgdb` or `qemu`) and endpoint configured.
- Command run and full output. For deploy failures, attach
`.gdb/<target>-<method>-loader.log`.
- What you expected, what happened instead.
- What you expected vs what happened.

## Submitting changes

1. Fork and branch from `main`.
2. Make focused commits — one logical change per commit. The git history
should read top-to-bottom as a small set of intentional steps.
3. Keep scripts shellcheck-clean (`shellcheck -x scripts/lib/common.sh
scripts/0*.sh`) and bash-syntax-clean (`bash -n`).
4. Update docs in the same PR. If you touched a script, check `README.md`
and `module/README.md` for anything the change makes inaccurate.
5. Open a PR with a description that explains *why* the change is needed,
not just what it does. The diff already says what.
1. Fork, branch from `main`.
2. Focused commits — one logical change per commit.
3. Keep scripts shellcheck-clean and `bash -n`-clean:
`shellcheck -x scripts/lib/common.sh scripts/0*.sh` and
`for f in scripts/lib/*.sh scripts/0*.sh; do bash -n "$f"; done`.
4. Update docs in the same PR — if you touched a script, check
`README.md` and `module/README.md` for anything the change made
inaccurate.
5. PR description explains *why*, not just what.

## Style

- **Shell:** tabs for indentation. `set -euo pipefail` at the top of every
script. Errors via the `die` helper. Use `lab_*` helpers from
`scripts/lib/common.sh` for SSH/SCP/rsync — do not call those binaries
directly from numbered scripts.
- **Makefile:** tabs for recipes. Explicit `.PHONY` declarations.
- **Markdown:** wrap prose at ~80 columns. Use fenced code blocks with
language tags.
- **JSON (VS Code config):** 2-space indent, trailing newline.

The repo includes an `.editorconfig` that captures these — most editors
will apply it automatically.

## Testing changes locally

There is no automated test suite for the build/debug flow (it requires real
hardware-or-VM targets). Before sending a PR:

- Run `bash -n scripts/0*.sh scripts/lib/*.sh` to catch syntax errors.
- If you have `shellcheck`, run it too: `shellcheck -x scripts/lib/common.sh
scripts/0*.sh`.
- Smoke-test the full chain against at least one target VM you have access
to: `01 --debug-symbols` → reboot → `02` → `03` → `04` → F5 in VS Code
→ set breakpoints in both your module and a kernel function and confirm
both bind.
- **Shell:** tabs for indent. `set -euo pipefail` at the top. Errors via
the `die` helper. Use `lab_*` helpers from `scripts/lib/common.sh` —
don't call ssh/scp/sshpass binaries directly from numbered scripts.
- **Makefile:** tabs for recipes; explicit `.PHONY`.
- **Markdown:** wrap at ~80; fenced code blocks with language tags.
- **JSON / YAML:** 2-space indent, trailing newline.

`.editorconfig` captures these — most editors apply it automatically.

## Testing locally

No automated test suite for the build/debug flow (it needs real VMs).
Before sending a PR, smoke-test the full chain against at least one
target VM: `01 --debug-symbols` → reboot → `02` → `03` → `04` → F5 in
VS Code → set breakpoints in both your module and a kernel function,
confirm both bind.
59 changes: 23 additions & 36 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,21 +1,15 @@
# Top-level wrapper that drives the user's kernel module under module/.
# Top-level wrapper for the user's module under module/.
#
# The user's module is built out-of-tree against a target-specific kernel
# header tree cached under .kernel-cache/<target>/. Build artifacts go under
# build/intermediate/<target>/<kernel>/ (Kbuild's M=) and the final .ko is
# moved to build/artifacts/<target>/<kernel>/.
# Builds out-of-tree against a target-specific kernel header tree under
# .kernel-cache/<target>/. Sources are staged into
# build/intermediate/<target>/<kernel>/ before Kbuild runs from there,
# and `-ffile-prefix-map=$(INTERMEDIATE_DIR)=$(MODULE_DIR)` is injected
# via KCFLAGS so DWARF paths and `__FILE__` resolve back to module/ —
# IDE breakpoints keyed by absolute path bind to the real sources.
#
# Staging exists for two reasons:
# 1. Per-target out-of-tree artifacts. Building the same module/ tree against
# multiple target kernels needs separate intermediate dirs.
# 2. DWARF path rewriting. -ffile-prefix-map=$(INTERMEDIATE_DIR)=$(MODULE_DIR)
# rewrites paths in DWARF debug info (and __FILE__) from the staged
# intermediate dir back to module/, so IDEs that bind breakpoints by
# absolute path (VS Code, CLion) match the symtab.
#
# The user's module/Makefile sees only a normal Kbuild invocation rooted at
# the staging directory. KCFLAGS is the kernel build's official escape hatch
# for adding C flags from the outside, so the user's Makefile stays generic.
# Per-target staging also keeps each (target, kernel) pair's object
# files isolated. The user's module/Makefile stays a normal Kbuild file
# with no knowledge of the staging.

HOST_KERNEL := $(shell uname -r)
CURRENT_CACHE := $(CURDIR)/.kernel-cache/current
Expand All @@ -40,16 +34,16 @@ help:
@echo "Top-level lab Makefile."
@echo ""
@echo "Normally invoked by scripts/03-build-module.sh <target>, which sets"
@echo "KDIR, BUILD_ID, INTERMEDIATE_DIR, and ARTIFACT_DIR to per-target paths."
@echo "KDIR, BUILD_ID, INTERMEDIATE_DIR, and ARTIFACT_DIR per-target."
@echo ""
@echo "Build targets:"
@echo " modules build module/ against KDIR (default)"
@echo " clean remove intermediate and artifact dirs for BUILD_ID"
@echo " prepare-build stage module/ into INTERMEDIATE_DIR (internal)"
@echo " modules build module/ against KDIR (default)"
@echo " clean remove BUILD_ID's intermediate + artifact dirs"
@echo " prepare-build stage module/ into INTERMEDIATE_DIR (internal)"
@echo ""
@echo "module/ ergonomics:"
@echo " use-example NAME=chuck_norise copy an example into module/"
@echo " clean-module reset module/ to just README.md"
@echo " use-example NAME=chuck_norise copy an example into module/"
@echo " clean-module reset module/ to just README.md"
@echo ""
@echo "Vars (auto-resolved when .kernel-cache/current points at a target):"
@echo " MODULE_DIR $(MODULE_DIR)"
Expand All @@ -59,8 +53,9 @@ help:
@echo " ARTIFACT_DIR $(ARTIFACT_DIR)"
@echo " EXTRA_CCFLAGS $(EXTRA_CCFLAGS) (appended to KCFLAGS)"

# Copy examples/$(NAME)/ into module/. Refuses if module/ already contains a
# Makefile/Kbuild (use clean-module first). NAME=chuck_norise unless overridden.
# Copy examples/$(NAME)/ into module/. Refuses if module/ already has a
# Makefile/Kbuild (run clean-module first). Preserves the placeholder
# module/README.md so the example's own README doesn't overwrite it.
NAME ?= chuck_norise
use-example:
@if [ ! -d "$(CURDIR)/examples/$(NAME)" ]; then \
Expand All @@ -80,14 +75,8 @@ use-example:
@echo "done. next: scripts/03-build-module.sh <target>"
@echo "(the example's docs stay at examples/$(NAME)/README.md)"

# Remove everything from module/ except its placeholder README, so you can
# drop a fresh module project in. Refuses to touch tracked files outside
# module/.
clean-module:
@if [ ! -d "$(MODULE_DIR)" ]; then \
echo "Makefile: $(MODULE_DIR) is missing — nothing to clean." >&2; \
exit 1; \
fi
@[ -d "$(MODULE_DIR)" ] || { echo "Makefile: $(MODULE_DIR) is missing." >&2; exit 1; }
@find "$(MODULE_DIR)" -mindepth 1 -maxdepth 1 -not -name README.md -print0 \
| xargs -0 -r rm -rf
@echo "module/ reset (kept only README.md)"
Expand All @@ -108,11 +97,9 @@ modules: prepare-build
echo "built $(ARTIFACT_DIR)/$$name"; \
done

# Mirror $(MODULE_DIR) into $(INTERMEDIATE_DIR) as a tree of absolute
# symlinks so Kbuild's `M=` sees the user's sources in a writeable scratch
# dir without polluting module/. `cp -as` is GNU coreutils; it recursively
# creates dirs and symlinks each file. Re-running is idempotent because we
# wipe the symlink scaffolding first.
# `cp -as` recursively creates dirs and absolute symlinks for each file.
# Re-runs are idempotent: we wipe the symlink scaffolding first so a
# removed source file doesn't linger as a stale symlink.
prepare-build:
@if [ ! -e "$(MODULE_DIR)/Makefile" ] && [ ! -e "$(MODULE_DIR)/Kbuild" ]; then \
echo "Makefile: no Makefile or Kbuild under $(MODULE_DIR);" >&2; \
Expand Down
Loading
Loading