Skip to content

Commit f12b521

Browse files
author
Jacob Pradels
committed
initial commit
0 parents  commit f12b521

25 files changed

Lines changed: 3287 additions & 0 deletions

.clangd

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Tell clangd how the BPF units are compiled (mirrors build/bpf.mk), so the
2+
# editor resolves vmlinux.h, the libbpf headers, and the __u* types instead of
3+
# flagging them. vmlinux.h is generated by `make`, so run it once for full
4+
# resolution.
5+
#
6+
# -I../v/include points at the vendored libbpf SDK headers when editing inside
7+
# the bootstrap repo (where v/ is a sibling). In a scaffolded project the
8+
# headers live in the shared toolchain cache instead; clangd harmlessly
9+
# ignores the missing path there. clangd resolves these relative to the
10+
# project root (the compile working directory).
11+
CompileFlags:
12+
Add:
13+
- -target
14+
- bpf
15+
- -Isrc/bpf/include
16+
- -I../v/include
17+
- -D__BPF_TRACING__
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
name: kernel-matrix
2+
3+
# Build the BPF object once per job, then boot a range of kernels and confirm
4+
# each one's verifier accepts every program in bin/probe.bpf.o. The check is
5+
# the vendored static `veristat` (it loads each program and reports a verdict);
6+
# kernels come from cilium's little-vm-helper (quay.io/lvh-images), booted under
7+
# QEMU/KVM on the runner. Each job writes a detail table to its step summary and
8+
# uploads its result; the final `matrix` job pivots them into one ✅/❌ grid.
9+
#
10+
# Tune `matrix.kernel` to the kernel lines your script must support (`6.6`,
11+
# `bpf-next`, …); available lines live at
12+
# https://quay.io/repository/lvh-images/kind?tab=tags. Each line is resolved to
13+
# a concrete image at run time rather than using the floating `<ver>-main` tag,
14+
# which the action can't consume: little-vm-helper@v0.0.30 derives the VM image
15+
# filename by stripping a trailing *numeric* build stamp, so a `-main` tag
16+
# yields a name that doesn't match the file `lvh` actually unpacks and the run
17+
# dies with "invalid reference format". So each job looks up the newest
18+
# date-stamped tag (`<ver>-YYYYMMDD.HHMMSS`, which the action handles) — always
19+
# tracking the latest build, with no tag to bump and immune to quay's pruning of
20+
# old stamps. The example probes are CO-RE tracepoint programs, so they need a
21+
# BTF-capable kernel (~5.4+); a program that uses newer features (ringbuf,
22+
# sched_ext, …) will legitimately fail to load on kernels that predate them —
23+
# which is exactly what this matrix surfaces.
24+
25+
on:
26+
workflow_dispatch:
27+
push:
28+
branches: [master, main]
29+
pull_request:
30+
31+
permissions:
32+
contents: read
33+
34+
jobs:
35+
verify:
36+
runs-on: ubuntu-latest
37+
strategy:
38+
fail-fast: false
39+
matrix:
40+
# Kernel lines to verify. Each is resolved to its newest date-stamped
41+
# lvh image at run time (see the header).
42+
kernel:
43+
- '6.1'
44+
- '6.6'
45+
- '6.12'
46+
- 'bpf-next'
47+
name: kernel ${{ matrix.kernel }}
48+
steps:
49+
- uses: actions/checkout@v4
50+
51+
- name: Resolve newest lvh image tag
52+
id: img
53+
env:
54+
KERNEL: ${{ matrix.kernel }}
55+
run: |
56+
set -euo pipefail
57+
# Newest <line>-YYYYMMDD.HHMMSS tag (date-stamps sort
58+
# lexicographically, so tail -1 is the most recent build).
59+
newest="$(curl -sf "https://quay.io/api/v1/repository/lvh-images/kind/tag/?onlyActiveTags=true&limit=100&filter_tag_name=like:${KERNEL}-" \
60+
| jq -r '.tags[].name' \
61+
| grep -E "^${KERNEL}-[0-9]{8}\.[0-9]+$" | sort | tail -1)"
62+
[ -n "$newest" ] || { echo "::error::no date-stamped tag found for kernel line '${KERNEL}'"; exit 1; }
63+
echo "resolved ${KERNEL} -> ${newest}"
64+
echo "tag=${newest}" >> "$GITHUB_OUTPUT"
65+
66+
- name: Build BPF object + stage veristat
67+
run: |
68+
set -euo pipefail
69+
# Builds bin/probe.bpf.o with the vendored static toolchain, also
70+
# populating the per-machine toolchain cache (clang/bpftool/veristat).
71+
make bpf
72+
73+
# Resolve the vendored static veristat the same way build/toolchain.mk
74+
# does, and stage it into bin/ so the VM finds it under /host. It is
75+
# fully static, so it runs in any kernel image's rootfs.
76+
. build/toolchain.lock
77+
arch="$(uname -m)"; [ "$arch" = arm64 ] && arch=aarch64
78+
cache="${XDG_CACHE_HOME:-$HOME/.cache}/yeet/toolchain/v${TOOLCHAIN_VERSION}/${arch}"
79+
if [ ! -x "$cache/veristat" ]; then
80+
echo "::error::veristat is not in the pinned toolchain (v${TOOLCHAIN_VERSION}). Bump build/toolchain.lock to a toolchain release that ships veristat."
81+
exit 1
82+
fi
83+
install -Dm755 "$cache/veristat" bin/veristat
84+
file bin/veristat bin/probe.bpf.o
85+
86+
- name: Verify on kernel ${{ matrix.kernel }}
87+
uses: cilium/little-vm-helper@v0.0.30
88+
with:
89+
test-name: veristat-${{ matrix.kernel }}
90+
image: kind
91+
image-version: ${{ steps.img.outputs.tag }}
92+
host-mount: ${{ github.workspace }}
93+
install-dependencies: 'true'
94+
cmd: |
95+
cd /host
96+
OUT_CSV=/host/.kmatrix/result.csv sh build/verify-kernel.sh
97+
98+
- name: Render kernel summary
99+
if: always()
100+
env:
101+
KVER: ${{ matrix.kernel }}
102+
KCSV: ${{ github.workspace }}/.kmatrix/result.csv
103+
run: |
104+
python3 - <<'PY' >> "$GITHUB_STEP_SUMMARY"
105+
import csv, os
106+
kver, path = os.environ["KVER"], os.environ["KCSV"]
107+
if not os.path.exists(path):
108+
print(f"### kernel `{kver}` — ⚠️ no result (build or boot failed)\n")
109+
raise SystemExit
110+
rows = list(csv.DictReader(open(path)))
111+
mark = lambda v: "✅" if v == "success" else "❌"
112+
ok = all(r["verdict"] == "success" for r in rows)
113+
head = "✅ all programs loaded" if ok else "❌ verifier rejected a program"
114+
print(f"### kernel `{kver}` — {head}\n")
115+
print("| Program | Verdict | Insns | States |")
116+
print("|---|:---:|--:|--:|")
117+
for r in rows:
118+
print(f"| `{r['prog_name']}` | {mark(r['verdict'])} | {r['total_insns']} | {r['total_states']} |")
119+
print()
120+
PY
121+
122+
- name: Upload result
123+
if: always()
124+
uses: actions/upload-artifact@v4
125+
with:
126+
name: kmatrix-${{ matrix.kernel }}
127+
path: ${{ github.workspace }}/.kmatrix/result.csv
128+
if-no-files-found: ignore
129+
130+
matrix:
131+
needs: verify
132+
if: always()
133+
runs-on: ubuntu-latest
134+
name: matrix summary
135+
steps:
136+
- uses: actions/download-artifact@v4
137+
with:
138+
path: results
139+
pattern: kmatrix-*
140+
141+
- name: Render matrix
142+
run: |
143+
python3 - <<'PY' >> "$GITHUB_STEP_SUMMARY"
144+
import csv, glob, os, re
145+
146+
# One CSV per kernel under results/kmatrix-<kernel>/result.csv.
147+
data, kernels, progs = {}, [], []
148+
for d in sorted(glob.glob("results/kmatrix-*")):
149+
kver = os.path.basename(d)[len("kmatrix-"):]
150+
f = os.path.join(d, "result.csv")
151+
if not os.path.exists(f):
152+
data[kver] = None
153+
kernels.append(kver)
154+
continue
155+
data[kver] = {r["prog_name"]: r["verdict"] for r in csv.DictReader(open(f))}
156+
kernels.append(kver)
157+
for p in data[kver]:
158+
if p not in progs:
159+
progs.append(p)
160+
161+
# Order kernels by version, bpf-next last.
162+
def keyf(k):
163+
m = re.match(r"(\d+)\.(\d+)", k)
164+
return (1, 0, 0) if not m else (0, int(m.group(1)), int(m.group(2)))
165+
kernels.sort(key=keyf)
166+
short = lambda k: re.sub(r"-(main|\d{8}\.\d+)$", "", k)
167+
168+
print("## 🐧 Kernel verification matrix\n")
169+
if not progs:
170+
print("⚠️ No results were produced — check the per-kernel job logs.\n")
171+
raise SystemExit
172+
print("| Program | " + " | ".join(short(k) for k in kernels) + " |")
173+
print("|---|" + "|".join(":-:" for _ in kernels) + "|")
174+
fail = 0
175+
for p in progs:
176+
cells = []
177+
for k in kernels:
178+
d = data[k]
179+
if d is None or p not in d:
180+
cells.append("⚪")
181+
elif d[p] == "success":
182+
cells.append("✅")
183+
else:
184+
cells.append("❌"); fail += 1
185+
print(f"| `{p}` | " + " | ".join(cells) + " |")
186+
print()
187+
print("✅ accepted · ❌ rejected · ⚪ not run\n")
188+
total = len(progs) * len([k for k in kernels if data[k] is not None])
189+
verb = "all programs loaded on every kernel" if fail == 0 else f"{fail} of {total} program×kernel checks failed"
190+
print(f"**{len(progs)} program(s) × {len(kernels)} kernel(s) — {verb}.**")
191+
PY
192+
193+
- name: Gate on any rejection
194+
run: |
195+
# Fail the run if any per-kernel job failed (a rejection or a build/boot error).
196+
if [ "${{ contains(needs.verify.result, 'failure') }}" = "true" ] || [ "${{ needs.verify.result }}" = "failure" ]; then
197+
echo "::error::one or more kernels rejected a program (see the matrix summary)"
198+
exit 1
199+
fi

.gitignore

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# build artifacts
2+
/node_modules/
3+
/src/index.jsx
4+
/.build/
5+
6+
# compiled BPF objects + generated CO-RE header
7+
/bin/*
8+
!/bin/.gitkeep
9+
/src/bpf/include/vmlinux.h
10+
11+
# kernel-matrix run output (build/kernel-matrix.sh)
12+
/.kmatrix/

0 commit comments

Comments
 (0)