Skip to content

M1 The toolkit #2

Description

@tamnd

Roughly fourteen weeks. The milestone with no reader visible output, and therefore the one most likely to get cut short when a deadline looms.

It must not be. There are 103 lessons and 60 blueprints coming, which is far past the point where hand work scales. Every later milestone borrows its speed from this one, and a weak toolkit here shows up as three parts that ship late.

Exit criterion. All nine visual primitives exist in both widget form and animation form, driven from the same kxray.models objects. bpc generates sections 2, 5 and 7 for five blueprints. kxdiff demonstrates all five strictness levels on real traces. The whole CI matrix is green.

kxray

  • kxray.btf: types, fields, offsets, sizes, enum values, function prototypes, and the type tags for __user, __rcu, __percpu and __iomem. All nineteen kinds read, holes found, bitfields in both encodings, ops tables picked out of a struct of function pointers. __iomem is the one that cannot be delivered as written: it is a sparse annotation the compiler drops, so BTF never records it, and asking for it is refused with the reason instead of answered with an empty list. The other three are queryable through Btf.annotated. One caveat that stays open: everything here has only ever been run against the handwritten fixture, because there is no built kernel yet. The first real /sys/kernel/btf/vmlinux read is what will find whatever this got wrong.
  • kxray.trace: function_graph, the flat function tracer, and trace_pipe events read through each event's own format file rather than a hard coded layout. All three are in, across Ask BTF which pointers the kernel is not allowed to follow #55, Read the flat function tracer, and the flags column it prints #56 and Read trace events through each event's own format file #57. function_graph gives the shape and the durations, the flat tracer gives the flags column and so the execution context, and events give named fields read through the layout the kernel publishes for them. Flags.context maps the column onto five of the six contexts in kxray/vocabulary.py and refuses to guess at atomic, because a held spinlock and a bare preempt_disable() raise the same counter and the column cannot tell them apart. The event reader loads /sys/kernel/tracing/events/<group>/<event>/format, so prev_state being four bytes on this 32-bit box and eight on a 64-bit one is read correctly on both.
  • kxray.proc: the /proc and /sys files the lessons actually use, each parser recording the ABI stability class of the file it read. In with Read /proc, and record what the kernel promises about each file #58. Five readers for four file shapes and eight real Tier 0 captures, and every read carries a Promise from kxray/proc/stability.py citing the file in the kernel tree that makes the claim. The number that made the ledger worth building: on 7.2.2 there are 685 files under Documentation/ABI and six of them describe a path in /proc, and not one of those six is a file anybody reads.
  • kxray.source: symbol to file and line, MAINTAINERS lookup, Kconfig symbols, the syscall tables, and citations with their context hash (Read the kernel's own source files #59)
  • kxray.models: the shared intermediate representation, so a trace renders as a tape, an animation and a blueprint table without three parsers (One arrangement of a trace, instead of three #60). Two pieces. The 1424 line module is a six file package now, split by subject, with a re-exporting __init__ so every caller was left alone. And the reason for the split: kxshapes/scene.py is the one conversion from a trace to a picture, where there used to be three. The tape widget built its own cells and wrote its own hover text, the blueprint compiler printed a tree and drew nothing, and a storyboard listed the shapes it used in a hand written field with no capture behind it. All three take a Scene now, and the storyboard field is checked against a scene built from the file it names, which is what stops a beat claiming CPU lanes over a uniprocessor capture.
  • kxray.replay: stepped playback of a recorded Tier 1 session, with the first recording, Linux 7.2.2 built for User Mode Linux and booted in the pinned container (kxray.replay: step through a recorded Tier 1 session #61)
  • kxray.corpus: loading, plus the normalisation that makes any of this diffable (kxray.corpus: load a pinned capture, and take the run out of it #62)

kxbox

  • Three boot profiles: teaching, lockdep and memcheck (kxbox: three boot profiles, and the kasan one cannot exist #63. Not kasan. arch/x86/Kconfig selects HAVE_ARCH_KASAN only if X86_64 and Tier 0 is 32 bit, so the symbol does not exist here and Kconfig drops it without a word. config/lockdep.config had been asking for it since it was written and no kernel this project built ever had it. The third profile is KFENCE, page poisoning and the slab debugger instead, kxbox/profiles.py is the registry, and boot() now refuses a name nothing builds.)
  • The bridge API: boot, sh, read, trace, insmod (kxbox: the bridge API on both backends, checked by signature #65. All five existed, two of them did not work the same way on both backends, and nothing was checking. insmod on a recording looked the recipe up under a name no recipe has ever used, so that path had never worked at all, and max_depth, documented as the fix for a guest that appears to hang, was on the live backend only and could not be reached from a lesson. What the PR really adds is a test comparing the two backends signature by signature, plus one asserting Box forwards every argument, because an argument one backend has and the other does not fails on the reader's machine rather than on the author's. write is left off a Box on purpose and there is a test saying so.)
  • The rootfs recipe, with busybox, strace and our own tools (rootfs: strace in the image, built from source and pinned by checksum #66. strace was the missing third. Built from the release tarball rather than downloaded, because nobody publishes a static i686 one, which cost about a megabyte compressed and thirty seven minutes of qemu translating x86 one instruction at a time. Both are measured and written down in RESULTS.md, and the binary is cached so the second cost happens once. What it buys is the one question a function graph tape cannot answer: what the program asked for, with the arguments, the flags and the errno on the way back. On writebyte it shows one write between the two writes to tracing_on, which is the claim all three of our programs are built on and which nothing was checking.)
  • The banner, showing version, architecture, profile, preemption model and which backend is live (banner: every line says where its fact came from #67. All five, and the part worth having is that every line says where its fact came from. The release and the preemption model are read off /proc/version on both backends, never filled in from pin.toml, and when the running kernel disagrees with the pin the banner says so. The architecture is copied out of the config fragments and the line says per its fragments, because nothing in the guest names one and printing three facts in one voice would let a reader believe a config file had been checked against something. Two hardcoded claims went away in the process. Verified against a real v86 boot in headless Chrome, which is the only place the live wording exists, and the page now prints it so a person can look at it.)

kxwidgets and kxmanim

  • The nine primitives, once each, shared between widgets and animations. They live in kxshapes as data, kxwidgets/shapes.py draws them as HTML, kxmanim draws them as video, and a test asserts the widget side covers all nine. SyscallTape and OpsExplorer were rewired to go through the shapes rather than keep their own copies, so there is one implementation of a trace cell and one of a socket.
  • The four signature artifacts: the Syscall Tape, the Layer Descent, the Lock Timeline and the Structure Map (SyscallTape, Descent, LockTimeline, and StructMap with ObjectGraph for the pointer side. The Lock Timeline needed a new capture, in corpora/traces/tier1/contended-lock.txt, because there was no evidence of a contended lock anywhere in the repository. It will not call a wait contention unless it has been told the clock behind the trace was real.)
  • Tape diff mode, since one trace is data and two traces are a transformation (TapeDiff. The verdict is kxdiff's. What the widget adds is the ring on functions that are only on one side, the faded boxes for frames the policy took out of the comparison, and shared_scale for when the question is whether something got faster.)

Checkers

  • bpc, generating sections 2, 5 and 7, failing the build on any diff. The tool is done and there are two layers against a hand edit: the seal hash, and a dry run regeneration that catches an edit which was resealed afterwards. The exit criterion asks for five blueprints and there are four, page-fault, write-path, lock-ordering and trace-ring-buffer, so the remaining work on this line is one more blueprint rather than any compiler. Each of the last two made the tool better anyway. The third: section 5 could only draw a function_graph capture, and every other kind of artefact rendered as one line reading Tracer unknown, so a blueprint resting on a lock report and two /proc readings had nothing observable in it. It now dispatches on the same routing table tools/baseline accounts lines through, and draws a before and after pair of one file as a single table with the change in it. The fourth: a stats file full of counters that answer no question on their own, so the generator now does the addition and the division and prints what was written, what share of it was thrown away, and how much history the buffer was holding.
  • kxdiff, with exact, sequence, set, counters and distribution modes. The four name based levels are a chain and a test checks that on the real corpus. distribution is deliberately outside it, because it is about the numbers every other level throws away.
  • claimledger, enforcing evidence, the config and architecture declaration, and the cap of two unobservable claims per lesson. The declaration half was the part that was missing. A lesson says which kernel, architecture, profile and tier it is about, an artefact says which machine it came off, and nothing compared the two. Now the profile has to be one pin.toml builds, the version is read through that profile so C-longterm is held to the fallback tree, and evidence that disagrees has to carry a why_not_pinned reason. It found four claims, including one whose evidence kind meant nothing was checking it at all.
  • tools/coverage, so a lesson citing a subsystem with no owner fails CI
  • tools/lintnb, enforcing the seven point notebook contract
  • The unparsed line baseline, so parser drift becomes a build failure instead of something a reader finds. Every line of every committed artefact is now read, skipped or unparsed, the three have to add up to the length of the file, and corpora/BASELINE.toml holds the numbers. The bucket that matters turned out to be skipped, not unparsed: four parsers had a bare continue in the middle of a loop, and a line one of those walks past looks exactly like a line that was never there.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/buildThe kernel build matrix, containers, CI and the drift botarea/siteThe book site, WASM export, versioning, hosting and accessibilitykind/milestoneA milestone checklist, tracking the issues that close itkind/toolingkxray, kxprobe, kxbox, bpc, the widgets and the checkerspriority/p0Blocks the current milestone

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions