rootfs: strace in the image, built from source and pinned by checksum - #66
Merged
Conversation
The image had no way to say which system calls a program made. A function graph tape says what the kernel did once it was inside a call; the arguments, the flags and the errno on the way back are not in it and no amount of reading one recovers them. strace is built rather than downloaded because nobody publishes a static i686 one. It costs about a megabyte compressed, which more than doubles the initramfs, and about thirty seven minutes to build on an arm laptop, which is qemu translating x86 one instruction at a time. The result is cached beside busybox so it happens once, and two builds from the same tarball in two fresh containers came out with the same sha256. build.sh had three near identical pin readers and was about to get a fourth, so there is one now that takes the section as an argument, and one fetch helper that will not hand back a file whose checksum does not match. Three tests check that build.sh only asks for keys the pin has, that the reader finds both call spellings, and that everything coming off the network is pinned. A smoke check runs strace and looks for what it printed, so a kernel that loses ptrace fails on its own line while everything else still passes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The M1 row is "The rootfs recipe, with busybox, strace and our own tools". Busybox and our own tools have been in since M0. strace was the missing third.
What it is for
A function graph tape says what the kernel did once it was inside a call, and how long each frame took. It does not say what the program asked for. The arguments, the file descriptors, the flags and the errno on the way back are not in a tape, and no amount of reading one recovers them, because the tracer records entry and exit and those values live in registers it never samples.
Those are the first questions anybody has. Here is
writebyte, one of the three programs already in the image, taken off a real boot:Everything
rootfs/README.mdclaims about that program is visible on that page. The warmup runs the whole sequence once and throws it away. The file is opened before the window rather than inside it. Between the two writes totracing_onthere is exactly onewrite, which is the claim all three programs are built on and which nothing was checking until this binary went in.Errno comes back named, which is the other half of what a tape cannot show:
Built, not downloaded
Busybox is an upstream binary and
pin.tomlexplains that as a deliberate trade. strace goes the other way for a duller reason than any argument: there is no static i686 strace to download. So it is built from the release tarball, in the same container the kernel is built in, and the tarball is what the checksum pins.Two costs, both measured rather than estimated.
Size. It is about a megabyte compressed, which more than doubles the initramfs.
abba.koMost of that megabyte is a static glibc plus the tables that turn a syscall number and six longs into a line somebody can read, and those tables are the entire feature. Alongside
A-fulla reader now fetches 4.91 MiB instead of 3.93 MiB, a quarter more. That is the same tradeB-btf-externalexists to price and it has the same honest answer: everything measured here is served from127.0.0.1, so this cannot settle it. What it does settle is the other half, which is that the boot did not get slower. Under node the box still reaches a shell in 6.3 seconds and all eleven checks pass.Build time. Thirty seven minutes on an arm laptop, where almost all of it is qemu translating x86 one instruction at a time. Configure alone was twenty five of those minutes, because it compiles and runs a few dozen small programs to decide things about the target. On an x86 machine it is a couple of minutes. Either way the binary is cached beside busybox, so it happens once, and the caching is why the number is bearable rather than a reason to skip the tool.
The build is reproducible in the way that matters here: two runs from the same tarball in two fresh containers produced a binary with the same sha256.
Three things it will not do, said up front
strace -kneeds libunwind and there is none, sostrace -VreportsOptional features enabled: (none).The
secondscolumn ofstrace -cis emulated time and means nothing, the same as every other duration in the box. The counts are real and the counts are what it is for:And attaching to a process stops it twice per system call, so a tape taken while strace is attached is a tape of ptrace doing its job, which is a real trace of something nobody asked about.
The tidy up underneath
build.shhad three pin readers that were the same function with a different section name in the middle, and strace was about to make it four. There is one now that takes the section as an argument, plus afetchhelper that downloads once and refuses to hand back a file whose sha256 does not match. Both things that come off the network go through it.That helper is also what made three tests worth writing, because every step of this build is now a key in a TOML file and a key spelled wrong is a
KeyErrorout of a heredoc partway through, which for strace means thirty seven minutes in on the machine most likely to be running it.Two calls reach the pin,
read_pin strace urlandread_strace urlthrough a one line helper, so the reader that finds them has its own test with a fixture rather than being trusted. The third asserts that everything with aurlin the pin also has a 64 character sha256, an https URL and a date saying when the checksum was taken.Checked
headless.js smokehas an eleventh entry now, and it goes through ptrace rather than the tracer, so a kernel that losesPTRACE_ATTACHor a binary built against the wrong libc fails on its own line while everything above it still passes.sh kxbox/rootfs/build.shwas then run once more with the cached binary deleted, so the fetch, the checksum, the container build and the pack were all exercised from nothing, and it came out at 1737376 bytes with the same strace inside it.Every gate locally: ruff check and format over 190 files, 1490 pytest tests with 7 skipped, 63 node tests, lintprose over 37 files, and the check runs of diagrams, nbbuild, sitebuild, kxbox, vendor, baseline, claimledger, coverage, bpc, kconfig, refcheck, lintnb and kxmanim.
What is deliberately not here
No strace capture is committed.
corpora/BASELINE.tomlrequires that every line of every committed artefact is accounted for by a reader that claims it, and there is nokxray.strace, so a capture dropped intocorpora/would be a file nothing has ever opened. The output above is in the PR and inrootfs/README.mdinstead.That is the right size for this row. A lesson can already do
box.sh("strace -o /tmp/x.txt prog")andbox.read("/tmp/x.txt")and print it, which is the whole of the Tourist use. A parser is what you need to diff two of them or draw one, and that is a kxray question rather than a rootfs question.