RFC: scx_utils, scx/compat: Recover enum64 values truncated by kernel BTF - #3708
Conversation
|
By the way, I checked w/ Google and this isn't an old pahole issue -- they're deliberately passing |
|
I'm going to add a GKE kernel to here (still debugging misc qol issues w/ it): #3700 ATM I'm tracking 3 recent compat issues:
|
Nice, yeah that would be cool. If you don't mind, could you please also add one for EKS? Basically any cloud provider running k8s that's on a sufficiently modern kernel would be good to get coverage on. |
Yeah so far I have steamos, fedora, amazonlinux, ubuntu, gke. I think that covers everything (although I was totally unaware of that gke issue earlier). |
htejun
left a comment
There was a problem hiding this comment.
Welcome back @ByteLab-David! Generally looks useful to me but I think it'd help to complain louder.
| WARN_ONCE.call_once(|| { | ||
| warn!( | ||
| "kernel BTF lacks ENUM64 encoding; substituting vmlinux.h values \ | ||
| for truncated 64-bit scx enums" |
There was a problem hiding this comment.
It'd be useful to explain the situation in more detail pointing to what may be missing and that this carries risk of wildly malfunctioning.
|
Claude-generated review. Suggestions:
|
Kernels whose BTF was generated without BTF_KIND_ENUM64 support encode
64-bit enums as 8-byte BTF_KIND_ENUM entries whose enumerator values are
truncated to the low 32 bits. This happens with pahole < 1.24, which
predates ENUM64, and with pahole passing --skip_encoding_btf_enum64.
Google's Container-Optimized OS (the GKE kernel) deliberately passes
--skip_encoding_btf_enum64 for backward compatibility with older BTF
consumers, so sched_ext functionally can't be used on GKE even on
>= 6.12 kernels.
On such kernels the runtime enum import reads SCX_DSQ_LOCAL as 0x2
instead of 0x8000000000000002, so every dispatch to a builtin DSQ
targets a bogus user DSQ id and the kernel ejects the scheduler with
"non-existent DSQ 0x2" (or "0x<cpu>" for LOCAL_ON dispatches) right
after attach. SCX_ENQ_PREEMPT/REENQ/LAST also read as 0, silently
dropping preempt semantics. Observed on GKE COS-129 nodes (kernel
6.12.77+):
```
ENUM 'scx_dsq_id_flags' encoding=UNSIGNED size=8 vlen=7
'SCX_DSQ_FLAG_BUILTIN' val=0
'SCX_DSQ_LOCAL' val=2
...
```
Let's work around this by detecting the truncation structurally in the
enum readers: an 8-byte BTF_KIND_ENUM can only be a downgraded 64-bit
enum, since any encoder with enum64 support would emit BTF_KIND_ENUM64
for those. When one is found, substitute the value from the vmlinux.h
the tree is built against, cross-checked against the low 32 bits the
kernel did provide:
- If the vmlinux.h value fits in 32 bits, the truncation was lossless and
the kernel's value stands as-is. This keeps enumerators that have moved
across kernel versions (e.g. SCX_ENQ_HEAD) working.
- If it exceeds 32 bits and the low bits match, substitute it and warn
once. The cross-check is vacuous for enumerators with no low bits set
(SCX_DSQ_FLAG_BUILTIN and friends), so the warning spells out exactly
what is being assumed and that a wrong substitution can make the
scheduler wildly malfunction.
- If the low bits don't match, refuse, as the ABI has drifted.
The recovery only applies to scx_-prefixed enum types: the substitution
table can never help other types, and read_enum() / __COMPAT_read_enum()
are generic public utilities, so non-scx 8-byte enums keep returning the
raw kernel value. Both refuse paths (low-32 mismatch and table miss) log
the failure before erroring out, as in-tree callers commonly swallow the
error with unwrap_or(0).
The substitution tables are generated from vmlinux.h by
scripts/gen_enum_defs.py (which previously only emitted the HAVE_*
defines) into scheds/include/scx/enums_abi.autogen.h and
rust/scx_utils/src/enums_abi.autogen.rs, so there's no hand-maintained
value list to keep in sync and newly added 64-bit enums are covered
whenever vmlinux.h and the tables are regenerated. Because the recovery
lives in __COMPAT_read_enum() / compat::read_enum(), it covers the
SCX_OPS_* and SCX_PICK_IDLE_* reads and read_enum_any() too, not just
the SCX_ENUM_INIT() import path.
To keep the autogen files from going stale, gen_vmlinux_h.sh now
regenerates them on every vmlinux.h update, and CI fails if rerunning
gen_enum_defs.py against the in-tree vmlinux.h produces any diff.
Regenerating already caught a stale enum_defs.autogen.h: vmlinux.h has
SCX_ENQ_GDSQ_FALLBACK but its HAVE_ define was missing, showing that
manual regeneration gets skipped -- and a stale table here would turn
the table-miss refusal into the common case.
Verified with the scx_utils unit tests and a new in-tree userspace test,
lib/selftests/compat/test_enum64_recovery, which is wired into the CI
unit-test job, pokes __COMPAT_recover_truncated_enum64() directly, and
mirrors the Rust tests (substitution, lossless truncation, low-32
mismatch, and table-miss cases plus ABI table spot-checks); the C and
Rust implementations must stay behaviorally identical.
Signed-off-by: David Vernet <void@mainfault.com>
2ee188c to
596dcf4
Compare
|
Thanks for the review @htejun. Addressed all feedback. |
multics69
left a comment
There was a problem hiding this comment.
Welcome back @ByteLab-David ! Looks good to me.
TL;DR: Some >= 6.12 kernels in the wild don't compile with a sufficiently modern pahole to get 64-bit enums in BTF. This is a proposed hacky workaround to generate u64s from vmlinux.h rather than relying on runtime BTF
This is admittedly kind of shitty and hacky, but practically speaking I think it should work fine, and the alternative is that shit just won't work on those platforms at all until they compile with the correct pahole. The worst part of this change is probably the logic added to gen_enum_defs.py. It's not great, but OTOH it should be fine and presumably isn't going to change or break as long as bpftool doesn't change how it emits vmlinux.h.
Here's the PR summary:
Kernels whose BTF was generated without BTF_KIND_ENUM64 support encode 64-bit enums as 8-byte BTF_KIND_ENUM entries whose enumerator values are truncated to the low 32 bits. This happens with pahole < 1.24, which predates ENUM64 (and with newer pahole passing
--skip_encoding_btf_enum64). Google's Container-Optimized OS (the GKE kernel) uses an older version of pahole, so it functionally can't be used with sched_ext even on >= 6.12 kernels.
On such kernels the runtime enum import reads SCX_DSQ_LOCAL as 0x2 instead of 0x8000000000000002, so every dispatch to a builtin DSQ targets a bogus user DSQ id and the kernel ejects the scheduler with "non-existent DSQ 0x2" (or "0x" for LOCAL_ON dispatches) right after attach. SCX_ENQ_PREEMPT/REENQ/LAST also read as 0, silently dropping preempt semantics. Observed on GKE COS-129 nodes (kernel 6.12.77+):
Let's work around this by detecting the truncation structurally in the enum readers: an 8-byte BTF_KIND_ENUM can only be a downgraded 64-bit enum, since any encoder with enum64 support would emit BTF_KIND_ENUM64 for those. When one is found, substitute the value from the vmlinux.h the tree is built against, cross-checked against the low 32 bits the kernel did provide:
If the vmlinux.h value fits in 32 bits, the truncation was lossless and the kernel's value stands as-is. This keeps enumerators that have moved across kernel versions (e.g. SCX_ENQ_HEAD) working.
If it exceeds 32 bits and the low bits match, substitute it and warn once.
If the low bits don't match, refuse, as the ABI has drifted.
The substitution tables are generated from vmlinux.h by scripts/gen_enum_defs.py (which previously only emitted the HAVE_* defines) into scheds/include/scx/enums_abi.autogen.h and rust/scx_utils/src/enums_abi.autogen.rs, so there's no hand-maintained value list to keep in sync and newly added 64-bit enums are covered whenever vmlinux.h and the tables are regenerated. Because the recovery lives in COMPAT_read_enum() / compat::read_enum(), it covers the SCX_OPS* and SCX_PICK_IDLE* reads and read_enum_any() too, not just the SCX_ENUM_INIT() import path.
Regenerating also caught a stale enum_defs.autogen.h: vmlinux.h already has SCX_ENQ_GDSQ_FALLBACK but its HAVE_ define was missing.
Verified with the scx_utils unit tests and a small userspace harness poking __COMPAT_recover_truncated_enum64() directly (substitution, lossless, and mismatch cases).