From edef2d5952830f6d11f596593b65e9f0aa9886c3 Mon Sep 17 00:00:00 2001 From: Jo5ta Date: Sat, 11 Jul 2026 19:35:24 +0200 Subject: [PATCH] ci: runtime gate for kernel tracing on a stock kernel Adds a CI job that boots the clltk kernel modules in QEMU on the stock distro kernel - which is built WITHOUT CONFIG_CONSTRUCTORS - and checks that tracing registers and produces trace files. This locks in the module-notifier init path (1.7.4): if registration ever regresses to depending on constructors, this job fails. No kernel build: the distro kernel-core package provides a bootable image and kernel-devel the matching build tree. On x86_64 the image is a bzImage QEMU boots directly; on aarch64 it is an EFI zboot (zstd) wrapper, so the raw Image is extracted from the zboot payload. Runs under TCG, no KVM required. Verified in the CI container: stock kernel (CONFIG_CONSTRUCTORS off), 5 trace files, zero invalid-offset errors. Signed-off-by: Jo5ta --- .github/workflows/ci.yml | 27 ++++++ VERSION.md | 9 +- scripts/ci-cd/step_kernel_runtime.sh | 118 +++++++++++++++++++++++++++ 3 files changed, 153 insertions(+), 1 deletion(-) create mode 100755 scripts/ci-cd/step_kernel_runtime.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 45f010e..30d322b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -239,6 +239,33 @@ jobs: - name: Build kernel modules run: ./scripts/container.sh ./scripts/ci-cd/step_build_kernel_module.sh + # ============================================ + # Kernel module RUNTIME test on a stock distro kernel. + # Boots the modules in QEMU on the distro kernel (built WITHOUT + # CONFIG_CONSTRUCTORS) and checks tracing actually registers - the + # regression gate for the module-notifier init path. No kernel build. + # ============================================ + kernel-runtime: + runs-on: ubuntu-latest + needs: [changes, checks] + if: | + !failure() && !cancelled() && + (github.event_name == 'push' || + needs.changes.outputs.code == 'true' || + needs.changes.outputs.tests == 'true' || + needs.changes.outputs.ci == 'true') + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Load CI-Container + uses: ./.github/actions/load-container + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Kernel runtime test (stock kernel) + run: ./scripts/container.sh ./scripts/ci-cd/step_kernel_runtime.sh + # ============================================ # Static analysis (clang-tidy + cppcheck, semantic subset) # Single-TU semantic lint. Cheap gate for confidently-wrong local diff --git a/VERSION.md b/VERSION.md index 24d20f4..0405474 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1,6 +1,13 @@ -1.7.4 +1.7.5 # Change log +## 1.7.5 +- ci: kernel-module runtime gate. Boots the modules in QEMU on the stock distro kernel + (built without CONFIG_CONSTRUCTORS) and verifies tracing actually registers and produces + trace files - the regression gate for the module-notifier init path (1.7.4). No kernel + build: uses the distro kernel-core image (bzImage on x86_64; the raw Image extracted from + the EFI zboot payload on aarch64). + ## 1.7.4 - fix: kernel tracing registers via a module notifier, so it works on kernels built without CONFIG_CONSTRUCTORS (the default for most production kernels). Registration diff --git a/scripts/ci-cd/step_kernel_runtime.sh b/scripts/ci-cd/step_kernel_runtime.sh new file mode 100755 index 0000000..15affdf --- /dev/null +++ b/scripts/ci-cd/step_kernel_runtime.sh @@ -0,0 +1,118 @@ +#!/usr/bin/env bash +# Copyright (c) 2024, International Business Machines +# SPDX-License-Identifier: BSD-2-Clause-Patent + +# CI Step: kernel-module RUNTIME test on a STOCK distro kernel. +# +# The clltk kernel module registers tracing at load. Stock distro kernels are +# built WITHOUT CONFIG_CONSTRUCTORS, so the constructor-based path never runs - +# registration has to come from the module notifier. This boots the modules in +# QEMU on the distro kernel and checks that tracing actually registers (trace +# files created, no invalid-offset errors). It is the regression gate for the +# notifier init path. +# +# No kernel build: the distro kernel-core package ships a bootable image and +# kernel-devel the matching build tree. Runs under TCG, so no KVM is required. +# Exit: 0 = tracing works on the stock kernel, 1 = it does not. + +set -euo pipefail +ROOT_PATH=$(git rev-parse --show-toplevel) +cd "${ROOT_PATH}" + +echo "========================================" +echo "CI Step: Kernel Runtime (stock kernel)" +echo "========================================" + +ARCH=$(uname -m) +case "$ARCH" in + x86_64) QEMU_PKG=qemu-system-x86-core ;; + aarch64) QEMU_PKG=qemu-system-aarch64-core ;; + *) echo "unsupported arch $ARCH"; exit 1 ;; +esac +dnf -y install kernel-core kernel-devel kernel-modules-core cpio zstd busybox "$QEMU_PKG" >/dev/null + +# Pick a version that has both a bootable image and a matching build tree. +KVER="" +for v in $(rpm -q --qf '%{version}-%{release}.%{arch}\n' kernel-core | sort -V -r); do + [ -f "/lib/modules/$v/vmlinuz" ] && [ -d "/usr/src/kernels/$v" ] && { KVER=$v; break; } +done +[ -n "$KVER" ] || { echo "no kernel-core/kernel-devel pair found"; exit 1; } +KSRC="/usr/src/kernels/$KVER" +CTORS=$(grep -c '^CONFIG_CONSTRUCTORS=y' "$KSRC/.config" || true) +echo "kernel $KVER (CONFIG_CONSTRUCTORS=y count: $CTORS -- 0 means the stock, notifier-only case)" + +# Kernel image. On x86 the distro vmlinuz is a bootable bzImage QEMU loads +# directly. On arm64 it is an EFI zboot (zstd) wrapper QEMU cannot load, so +# extract the raw Image from the zboot payload (offset/size from its header). +KIMG="/lib/modules/$KVER/vmlinuz" +if [ "$ARCH" != x86_64 ]; then + python3 - "$KIMG" /tmp/clltk_Image.zst <<'PY' +import sys +d = open(sys.argv[1], "rb").read() +x = d.find(b"zimg") +if x < 0: + raise SystemExit("not an EFI zboot image") +payload_offset = int.from_bytes(d[x + 4:x + 8], "little") +payload_size = int.from_bytes(d[x + 8:x + 12], "little") +open(sys.argv[2], "wb").write(d[payload_offset:payload_offset + payload_size]) +PY + zstd -qdf /tmp/clltk_Image.zst -o /tmp/clltk_Image + KIMG=/tmp/clltk_Image +fi + +echo "Building the kernel modules against $KVER..." +# cd instead of make -C: the module Makefile derives repo paths from $(PWD). +( cd kernel_tracing_library/src && make KERNEL_SRC="$KSRC" modules >/dev/null ) +make -C "$KSRC" M="$ROOT_PATH/examples/simple_kernel_module" \ + KBUILD_EXTRA_SYMBOLS="$ROOT_PATH/kernel_tracing_library/src/Module.symvers" modules >/dev/null + +# Minimal initramfs: busybox init loads the modules and reports the result. +IRD=$(mktemp -d) +mkdir -p "$IRD"/{bin,proc,sys,tmp} +cp "$(command -v busybox)" "$IRD/bin/busybox" +cp kernel_tracing_library/src/clltk_kernel_tracing.ko "$IRD/clltk.ko" +cp examples/simple_kernel_module/simple_tracing_test.ko "$IRD/example.ko" +cat > "$IRD/init" <<'INIT' +#!/bin/busybox sh +export PATH=/bin +busybox mount -t proc proc /proc +busybox mount -t sysfs sysfs /sys +busybox mount -t tmpfs tmpfs /tmp +busybox echo "GUEST_UP $(busybox uname -r)" +busybox insmod /clltk.ko tracing_path=/tmp/ +busybox insmod /example.ko +busybox sleep 3 +N=$(busybox ls /tmp/*.clltk_ktrace 2>/dev/null | busybox wc -l) +INV=$(busybox dmesg | busybox grep -c "invalid in_file_offset") +busybox echo "RESULT traces=$N invalid=$INV" +if [ "$N" -gt 0 ] && [ "$INV" -eq 0 ]; then + busybox echo STOCK_KERNEL_TRACE_OK +else + busybox echo STOCK_KERNEL_TRACE_FAIL +fi +busybox poweroff -f +INIT +chmod +x "$IRD/init" +( cd "$IRD" && find . | cpio -o -H newc 2>/dev/null | gzip ) > /tmp/clltk_initramfs.gz + +if [ "$ARCH" = x86_64 ]; then + QEMU=(qemu-system-x86_64 -M pc) + CONSOLE=ttyS0 + [ -w /dev/kvm ] && QEMU+=(-enable-kvm -cpu host) || QEMU+=(-cpu max) +else + QEMU=(qemu-system-aarch64 -M virt -cpu max) + CONSOLE=ttyAMA0 +fi + +echo "Booting the stock kernel in QEMU..." +timeout 420 "${QEMU[@]}" -m 1G -smp 4 -kernel "$KIMG" -initrd /tmp/clltk_initramfs.gz \ + -append "console=$CONSOLE panic=1 rdinit=/init" -nographic -no-reboot > /tmp/clltk_boot.log 2>&1 || true + +grep -aE "RESULT|STOCK_KERNEL_TRACE" /tmp/clltk_boot.log | tail -3 || true +if grep -aq STOCK_KERNEL_TRACE_OK /tmp/clltk_boot.log; then + echo "PASSED: kernel tracing registers and traces on a stock kernel" + exit 0 +fi +echo "FAILED: no traces on the stock kernel" +tail -25 /tmp/clltk_boot.log +exit 1