RFC: scx_utils, scx/compat: Recover enum64 values truncated by kernel BTF#3708
Open
ByteLab-David wants to merge 1 commit into
Open
RFC: scx_utils, scx/compat: Recover enum64 values truncated by kernel BTF#3708ByteLab-David wants to merge 1 commit into
ByteLab-David wants to merge 1 commit into
Conversation
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<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.
- 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).
Signed-off-by: David Vernet <void@mainfault.com>
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.
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).