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
28 changes: 28 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
root = true

[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
trim_trailing_whitespace = true

[*.{sh,bash}]
indent_style = tab

[Makefile]
indent_style = tab

[Kbuild]
indent_style = tab

[*.{json,md,yml,yaml}]
indent_style = space
indent_size = 2

[*.{c,h}]
indent_style = tab
indent_size = 8

[*.ps1]
indent_style = space
indent_size = 4
114 changes: 114 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
name: ci

on:
push:
branches: [main]
pull_request:

jobs:
shellcheck:
name: shellcheck + bash -n
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install shellcheck
run: sudo apt-get update && sudo apt-get install -y shellcheck

- name: bash -n (syntax check)
run: |
for f in scripts/lib/*.sh scripts/0*.sh; do
echo "::group::bash -n $f"
bash -n "$f"
echo "::endgroup::"
done

- name: shellcheck
run: shellcheck -x scripts/lib/common.sh scripts/0*.sh

json:
name: validate JSON
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: validate .vscode/*.json
run: |
for f in .vscode/*.json; do
echo "::group::python -m json.tool $f"
python3 -m json.tool "$f" > /dev/null
echo "::endgroup::"
done

makefile:
name: Makefile syntax
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: make help (dry-parse)
run: make help

build:
name: build example module against runner kernel (${{ matrix.os }})
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]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4

- name: install host kernel headers + build tools
run: |
sudo apt-get update
sudo apt-get install -y \
build-essential \
bc bison flex libelf-dev libssl-dev \
"linux-headers-$(uname -r)"

- name: use the example module
run: make use-example NAME=chuck_norise

- name: build via top-level Makefile (default target)
# KDIR defaults to /lib/modules/$(uname -r)/build when there's no
# .kernel-cache/current, so this exercises the full prepare-build +
# KCFLAGS pipeline against a real (the runner's) kernel.
run: make modules

- name: assert a .ko was produced
run: |
set -e
shopt -s nullglob
kos=(build/artifacts/*/*/*.ko)
if [ "${#kos[@]}" -eq 0 ]; then
echo "::error::no .ko produced under build/artifacts/"
ls -R build/ || true
exit 1
fi
for ko in "${kos[@]}"; do
echo "built $ko ($(stat -c %s "$ko") bytes)"
file "$ko"
done

- name: verify DWARF prefix-map rewrote paths back to module/
# The whole point of -ffile-prefix-map is that DWARF references your
# source under module/<...>.c, not build/intermediate/<...>.c. If a
# change to the Makefile breaks the injection, this catches it.
run: |
set -e
ko=$(find build/artifacts -name '*.ko' | head -1)
if objdump --dwarf=decodedline "$ko" 2>/dev/null \
| grep -qE '/build/intermediate/'; then
echo "::error::DWARF still references build/intermediate/ — prefix-map broken"
objdump --dwarf=decodedline "$ko" 2>/dev/null | grep -E '/build/intermediate/' | head
exit 1
fi
echo "ok: DWARF paths point at module/"

- name: clean-module resets cleanly
run: |
make clean-module
ls module/
[ "$(ls module/)" = "README.md" ] || { echo "::error::clean-module did not reduce module/ to just README.md"; exit 1; }
16 changes: 13 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
# Per-machine config (lab.example.env is the tracked template).
lab.local.env

# Per-target synced kernel headers, source, vmlinux.
.kernel-cache/

# Generated GDB init files (regenerated by scripts/04 on every F5).
.gdb/

# Per-target build intermediates and final .ko artifacts.
build/

# Kbuild stragglers in case anything runs out of place.
*.ko
*.mod
*.mod.c
Expand All @@ -12,10 +19,13 @@ build/
Module.symvers
modules.order

compile_commands.json
# IDE / editor noise.
.vscode/ipch/
.codex
kernel.gdb
.claude/

# Agent / session local notes (CLAUDE.md is consumed by Claude Code; keep it
# out of the public repo so workflow guidance stays separate from user docs).
CLAUDE.md
session.md
.claude/
todo.md
5 changes: 5 additions & 0 deletions .vscode/c_cpp_properties.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,10 @@
"${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."
]
}
4 changes: 2 additions & 2 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"recommendations": [
"ms-vscode.cpptools"
"ms-vscode.cpptools",
"webfreak.debug"
]
}

8 changes: 4 additions & 4 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
{
"id": "debugEndpoint",
"type": "promptString",
"description": "GDB remote endpoint (host:port). Used only by the Native Debug launch; cppdbg reads it from the generated .gdb script.",
"description": "GDB remote endpoint (host:port). Only used by Native Debug — cppdbg reads it from the generated .gdb script.",
"default": "127.0.0.1:1234"
}
],
"configurations": [
{
"name": "Kernel: Cppdbg Debug",
"name": "Kernel: cppdbg (full GDB MI)",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/.gdb/current-vmlinux",
Expand All @@ -21,7 +21,7 @@
"preLaunchTask": "Kernel: Deploy Debug",
"setupCommands": [
{
"description": "Attach GDB to the selected target",
"description": "Source the GDB init script generated by scripts/04",
"text": "source ${workspaceFolder}/.gdb/current-debug.gdb",
"ignoreFailures": false
}
Expand All @@ -30,7 +30,7 @@
"externalConsole": false
},
{
"name": "Kernel: Native Debug",
"name": "Kernel: Native Debug (faster, fewer features)",
"type": "gdb",
"request": "attach",
"executable": "${workspaceFolder}/.gdb/current-vmlinux",
Expand Down
37 changes: 7 additions & 30 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,38 +1,15 @@
{
"C_Cpp.default.configurationProvider": "",
"C_Cpp.default.configurationName": "Linux kernel module - current",
"C_Cpp.default.compileCommands": "",
"C_Cpp.default.compilerPath": "/usr/bin/gcc",
"C_Cpp.default.cStandard": "gnu11",
"C_Cpp.default.intelliSenseMode": "linux-gcc-x64",
"C_Cpp.default.defines": [
"__KERNEL__",
"MODULE",
"CC_USING_FENTRY",
"KBUILD_MODNAME=\"kmod\"",
"KBUILD_BASENAME=\"kmod\"",
"__KBUILD_MODNAME=kmod_kmod"
],
"C_Cpp.default.includePath": [
"${workspaceFolder}/module/**",
"${workspaceFolder}/.kernel-cache/current/build/include",
"${workspaceFolder}/.kernel-cache/current/build/include/uapi",
"${workspaceFolder}/.kernel-cache/current/build/include/generated",
"${workspaceFolder}/.kernel-cache/current/build/include/generated/uapi",
"${workspaceFolder}/.kernel-cache/current/build/ubuntu/include",
"${workspaceFolder}/.kernel-cache/current/build/arch/x86/include",
"${workspaceFolder}/.kernel-cache/current/build/arch/x86/include/uapi",
"${workspaceFolder}/.kernel-cache/current/build/arch/x86/include/generated",
"${workspaceFolder}/.kernel-cache/current/build/arch/x86/include/generated/uapi"
],
"C_Cpp.default.forcedInclude": [
"${workspaceFolder}/.kernel-cache/current/build/include/linux/compiler-version.h",
"${workspaceFolder}/.kernel-cache/current/build/include/linux/kconfig.h",
"${workspaceFolder}/.kernel-cache/current/build/include/linux/compiler_types.h"
],
"C_Cpp.errorSquiggles": "enabled",
"files.associations": {
"*.h": "c",
"*.c": "c"
"*.c": "c",
"Kbuild": "makefile"
},
"files.exclude": {
".kernel-cache": true,
".gdb": true,
"build": true
}
}
6 changes: 6 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@
}
],
"tasks": [
{
"label": "Kernel: Check Target",
"type": "shell",
"command": "./scripts/00-check-target.sh ${input:kernelTarget} ${input:debugMethod}",
"problemMatcher": []
},
{
"label": "Kernel: Provision Target",
"type": "shell",
Expand Down
83 changes: 83 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Contributing

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

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

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.

PRs that **belong** here:

- 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.

PRs that **do not** belong here:

- 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.

## 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
`.gdb/<target>-<method>-loader.log`.
- What you expected, what happened instead.

## 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.

## 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.
Loading
Loading