* [PATCH 00/58] Unit test framework for klp-build toolchain
@ 2026-09-11 18:42 Song Liu
2026-09-11 18:42 ` [PATCH 01/58] objtool: Add test harness for the klp subcommands Song Liu
` (26 more replies)
0 siblings, 27 replies; 77+ messages in thread
From: Song Liu @ 2026-09-11 18:42 UTC (permalink / raw)
To: live-patching
Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay,
kernel-team, Song Liu
This is the unit test part of a test framework for the klp-build toolchain.
The other part of the test framework will be some integration tests,
similar to an earlier propose [1].
This is joint work by Puranjay Mohan, Joe Lawrence, and myself (Song Liu).
Note that, while only two patches here are under Joe's authorship, his
contribution to this set is much bigger than the number represents. A lot
of these tests are based on Joe's earlier work in this area [2].
As the Assisted-by tags show, a lot of the work here is done with assists
of AI. We actually designed the framework to so that we expect AI to write
these tests in the future. AI also contributed in the design of the test
framework. However, we (human) carefully reviewed the test framework. Also,
this cover letter is 100% written by a human being (Song Liu).
Here are some key design choices we made for this unit tests framework:
1. We do not need different unit test here for different kernel CONFIG_*
options. Instead, the tests cover different CONFIG_* options by passing
different arguments to the compiler and objtool.
2. The test is NOT designed to be backward compatible. Similar to objtool
itself, the unit tests are delivered with the kernel tree. The user of
these tests should test the objtool with the unit tests in the same
branch. Once this test framework lands, future changes and fixes to the
objtool klp command should land with unit tests that cover the changes.
3. It is possible to run these tests on a different architecture with
proper cross compilers. However, for automated CI, we are planning to
focus on native tests. Please refer to "Future work" section below for
more information about the CI.
4. AI will help write tests. We found AI is very cable to write good tests.
tools/objtool/Documentation/klp-write-tests.txt is added for AI to read,
so that AI can write tests for this test framework.
Future work
1. The integration test simiar to [1]. Unlike the unit tests, the
integration tests will need separate runs to cover different kernel
CONFIG_* combinations.
2. CI. The plan is to build a lore => patchwork => GitHub CI framework.
This is the model used by subsystems such as netdev, bpf, and risc-v.
We also have some ideas on "who will pay for the CI" issue, but I would
rather not promise anything yet.
3. More tests.
Thanks,
Song
[1] https://lore.kernel.org/live-patching/20260226005436.379303-9-song@kernel.org/
[2] https://github.com/joe-lawrence/klp-build-unit-tests
Joe Lawrence (2):
objtool: Keep failing test workdirs by default
objtool: Forward toolchain variables to the klp test runner
Puranjay Mohan (16):
objtool: Add test harness for the klp subcommands
objtool/klp: Add test for rejecting changed data
objtool/klp: Add test for newly introduced data
objtool/klp: Add test for newly introduced functions
objtool/klp: Add test for static local correlation
objtool/klp: Add test for cold function halves
objtool/klp: Add test for special section extraction
objtool/klp: Add test for selective special section extraction
objtool/klp: Add test for jump table key relocations
objtool/klp: Add test for rejecting module-owned static branch keys
objtool/klp: Add test for rejecting module-owned static call keys
objtool/klp: Add test for symids in discarded sections
objtool/klp: Add test for rejecting references to init code/data
objtool/klp: Add test for correlation across ThinLTO name mangling
objtool/klp: Add test for objects without .modinfo
objtool/klp: Add test for unchecksummed input
Song Liu (40):
objtool/klp: Check the klp test environment once, before any test
objtool/klp: Group the klp tests by architecture
objtool/klp: Classify klp test outcomes
objtool/klp: Build klp test fixtures through the harness
objtool/klp: Grow the klp test harness vocabulary
objtool/klp: Give each run one working directory, one per test inside
it
objtool/klp: Run the klp tests under set -u
objtool/klp: Document the klp test harness
objtool/klp: Add klp diff and post-link regression tests
objtool/klp: Add test for klp reloc section naming in module objects
objtool/klp: Add test for vmlinux relocs in a patched module
objtool/klp: Add test for Module.symvers path normalization
objtool/klp: Add test for the contents of the klp_funcs list
objtool/klp: Add test for EXPORT_SYMBOL_FOR_MODULES references
objtool/klp: Add test for new references to exported symbols
objtool/klp: Add test for empty x86 alternative replacements
objtool/klp: Add test for recorded checksum values
objtool/klp: Add test for position-independent checksums
objtool/klp: Add test for sympos in module objects
objtool/klp: Add test for sympos resolved against a linked vmlinux
objtool/klp: Add test for static locals which must not be correlated
objtool/klp: Add test for __bug_table, __ex_table and __mcount_loc
extraction
objtool/klp: Add test for kCFI prefix symbols and traps
objtool/klp: Add test for symbols whose linkage the patch changes
objtool/klp: Test rejection of a file-local static branch key
objtool/klp: Test a hand-built livepatch module's static call keys
objtool/klp: Test text annotations on alternative replacements
objtool/klp: Add test for data object checksums
objtool/klp: Add test for symbols with no checksum entry of their own
objtool/klp: Add test for a static branch introduced by the patch
objtool/klp: Add test for tracepoint and pr_debug static branch keys
objtool/klp: Add test for a static call introduced by the patch
objtool/klp: Add test for instruction operand checksums
objtool/klp: Add test for alternative replacement code in checksums
objtool/klp: Add test for the alignment of cloned data sections
objtool/klp: Add test for a patch which strips a data annotation
objtool/klp: Add test for absolute and __ADDRESSABLE symbols
objtool/klp: Add test for UBSAN metadata in an unchanged function
objtool/klp: Add test for Clang switch jump tables
objtool/klp: Add test for ThinLTO symbols sharing a demangled name
.../objtool/Documentation/klp-test-design.txt | 276 ++++++
.../objtool/Documentation/klp-write-tests.txt | 260 ++++++
tools/objtool/Makefile | 7 +-
.../generic/fixtures/abs_and_addressable.c | 44 +
tools/objtool/tests/generic/fixtures/basic.c | 20 +
.../tests/generic/fixtures/changed_data.c | 16 +
.../tests/generic/fixtures/checksum_data.c | 116 +++
.../tests/generic/fixtures/checksum_insn.c | 78 ++
.../generic/fixtures/checksum_position.c | 45 +
.../tests/generic/fixtures/checksum_skip.c | 47 +
.../tests/generic/fixtures/cold_function.c | 21 +
.../tests/generic/fixtures/cross_module.c | 25 +
.../tests/generic/fixtures/data_alignment.c | 29 +
.../tests/generic/fixtures/function_removal.c | 25 +
.../tests/generic/fixtures/init_reference.c | 16 +
.../tests/generic/fixtures/jump_label.c | 76 ++
.../tests/generic/fixtures/klp_funcs.c | 31 +
.../tests/generic/fixtures/local_to_global.c | 34 +
.../objtool/tests/generic/fixtures/new_data.c | 18 +
.../tests/generic/fixtures/new_export_ref.c | 20 +
.../tests/generic/fixtures/new_function.c | 21 +
.../tests/generic/fixtures/no_modinfo.c | 11 +
.../tests/generic/fixtures/special_section.c | 24 +
.../generic/fixtures/special_section_shared.c | 31 +
.../tests/generic/fixtures/static_call.c | 53 ++
.../tests/generic/fixtures/static_local.c | 17 +
.../fixtures/static_local_uncorrelated.c | 41 +
.../tests/generic/fixtures/switch_rodata.c | 31 +
.../tests/generic/fixtures/symid_discarded.c | 25 +
.../tests/generic/fixtures/sympos_dup.c | 32 +
.../tests/generic/fixtures/sympos_vmlinux.c | 40 +
.../generic/fixtures/thinlto_ambiguity.c | 55 ++
.../tests/generic/fixtures/thinlto_local.c | 39 +
.../tests/generic/fixtures/ubsan_noise.c | 49 +
.../tests/generic/test-abs-and-addressable.sh | 50 ++
tools/objtool/tests/generic/test-basic.sh | 17 +
.../tests/generic/test-changed-data.sh | 18 +
.../tests/generic/test-checksum-data.sh | 61 ++
.../tests/generic/test-checksum-debug.sh | 49 +
.../tests/generic/test-checksum-insn.sh | 49 +
.../tests/generic/test-checksum-position.sh | 35 +
.../tests/generic/test-checksum-skip.sh | 74 ++
.../tests/generic/test-checksum-value.sh | 37 +
.../tests/generic/test-cold-function.sh | 30 +
.../tests/generic/test-data-alignment.sh | 40 +
.../generic/test-export-symbol-for-modules.sh | 39 +
.../tests/generic/test-function-removal.sh | 34 +
.../tests/generic/test-init-reference.sh | 16 +
.../generic/test-jump-label-exempt-keys.sh | 51 ++
.../tests/generic/test-jump-label-key.sh | 44 +
.../generic/test-jump-label-module-key.sh | 22 +
.../test-jump-label-module-static-key.sh | 45 +
.../tests/generic/test-jump-label-new-key.sh | 51 ++
.../tests/generic/test-klp-funcs-content.sh | 44 +
.../generic/test-local-to-global-flip.sh | 50 ++
.../tests/generic/test-local-vs-export.sh | 32 +
.../tests/generic/test-missing-checksum.sh | 18 +
.../tests/generic/test-missing-modinfo.sh | 16 +
.../tests/generic/test-modname-normalize.sh | 26 +
.../tests/generic/test-module-object.sh | 31 +
.../generic/test-module-vmlinux-reloc.sh | 40 +
tools/objtool/tests/generic/test-new-data.sh | 17 +
.../tests/generic/test-new-export-ref.sh | 34 +
.../tests/generic/test-new-function.sh | 17 +
tools/objtool/tests/generic/test-post-link.sh | 39 +
.../generic/test-special-section-shared.sh | 26 +
.../tests/generic/test-special-section.sh | 20 +
.../test-static-call-annotate-stripped.sh | 41 +
.../generic/test-static-call-module-key.sh | 25 +
.../tests/generic/test-static-call-new.sh | 45 +
.../generic/test-static-local-uncorrelated.sh | 40 +
.../tests/generic/test-static-local.sh | 24 +
.../tests/generic/test-switch-rodata.sh | 53 ++
.../tests/generic/test-symid-discarded.sh | 44 +
.../tests/generic/test-sympos-vmlinux.sh | 57 ++
tools/objtool/tests/generic/test-sympos.sh | 51 ++
.../tests/generic/test-symvers-parse-error.sh | 23 +
.../tests/generic/test-thinlto-ambiguity.sh | 77 ++
.../tests/generic/test-thinlto-local.sh | 48 +
.../objtool/tests/generic/test-ubsan-noise.sh | 48 +
tools/objtool/tests/lib.sh | 834 ++++++++++++++++++
tools/objtool/tests/run-tests.sh | 216 +++++
.../objtool/tests/x86/fixtures/alt_annotate.c | 57 ++
.../objtool/tests/x86/fixtures/checksum_alt.c | 60 ++
.../tests/x86/fixtures/empty_alternative.c | 77 ++
tools/objtool/tests/x86/fixtures/kcfi.c | 39 +
.../tests/x86/fixtures/special_sections.c | 57 ++
.../tests/x86/fixtures/static_call_no_key.c | 32 +
.../objtool/tests/x86/test-alt-annotation.sh | 38 +
tools/objtool/tests/x86/test-checksum-alt.sh | 45 +
.../tests/x86/test-empty-alternative.sh | 31 +
tools/objtool/tests/x86/test-kcfi.sh | 39 +
.../tests/x86/test-manual-klp-static-call.sh | 40 +
.../tests/x86/test-special-sections.sh | 42 +
94 files changed, 5027 insertions(+), 1 deletion(-)
create mode 100644 tools/objtool/Documentation/klp-test-design.txt
create mode 100644 tools/objtool/Documentation/klp-write-tests.txt
create mode 100644 tools/objtool/tests/generic/fixtures/abs_and_addressable.c
create mode 100644 tools/objtool/tests/generic/fixtures/basic.c
create mode 100644 tools/objtool/tests/generic/fixtures/changed_data.c
create mode 100644 tools/objtool/tests/generic/fixtures/checksum_data.c
create mode 100644 tools/objtool/tests/generic/fixtures/checksum_insn.c
create mode 100644 tools/objtool/tests/generic/fixtures/checksum_position.c
create mode 100644 tools/objtool/tests/generic/fixtures/checksum_skip.c
create mode 100644 tools/objtool/tests/generic/fixtures/cold_function.c
create mode 100644 tools/objtool/tests/generic/fixtures/cross_module.c
create mode 100644 tools/objtool/tests/generic/fixtures/data_alignment.c
create mode 100644 tools/objtool/tests/generic/fixtures/function_removal.c
create mode 100644 tools/objtool/tests/generic/fixtures/init_reference.c
create mode 100644 tools/objtool/tests/generic/fixtures/jump_label.c
create mode 100644 tools/objtool/tests/generic/fixtures/klp_funcs.c
create mode 100644 tools/objtool/tests/generic/fixtures/local_to_global.c
create mode 100644 tools/objtool/tests/generic/fixtures/new_data.c
create mode 100644 tools/objtool/tests/generic/fixtures/new_export_ref.c
create mode 100644 tools/objtool/tests/generic/fixtures/new_function.c
create mode 100644 tools/objtool/tests/generic/fixtures/no_modinfo.c
create mode 100644 tools/objtool/tests/generic/fixtures/special_section.c
create mode 100644 tools/objtool/tests/generic/fixtures/special_section_shared.c
create mode 100644 tools/objtool/tests/generic/fixtures/static_call.c
create mode 100644 tools/objtool/tests/generic/fixtures/static_local.c
create mode 100644 tools/objtool/tests/generic/fixtures/static_local_uncorrelated.c
create mode 100644 tools/objtool/tests/generic/fixtures/switch_rodata.c
create mode 100644 tools/objtool/tests/generic/fixtures/symid_discarded.c
create mode 100644 tools/objtool/tests/generic/fixtures/sympos_dup.c
create mode 100644 tools/objtool/tests/generic/fixtures/sympos_vmlinux.c
create mode 100644 tools/objtool/tests/generic/fixtures/thinlto_ambiguity.c
create mode 100644 tools/objtool/tests/generic/fixtures/thinlto_local.c
create mode 100644 tools/objtool/tests/generic/fixtures/ubsan_noise.c
create mode 100755 tools/objtool/tests/generic/test-abs-and-addressable.sh
create mode 100755 tools/objtool/tests/generic/test-basic.sh
create mode 100755 tools/objtool/tests/generic/test-changed-data.sh
create mode 100755 tools/objtool/tests/generic/test-checksum-data.sh
create mode 100755 tools/objtool/tests/generic/test-checksum-debug.sh
create mode 100755 tools/objtool/tests/generic/test-checksum-insn.sh
create mode 100755 tools/objtool/tests/generic/test-checksum-position.sh
create mode 100755 tools/objtool/tests/generic/test-checksum-skip.sh
create mode 100755 tools/objtool/tests/generic/test-checksum-value.sh
create mode 100755 tools/objtool/tests/generic/test-cold-function.sh
create mode 100755 tools/objtool/tests/generic/test-data-alignment.sh
create mode 100755 tools/objtool/tests/generic/test-export-symbol-for-modules.sh
create mode 100755 tools/objtool/tests/generic/test-function-removal.sh
create mode 100755 tools/objtool/tests/generic/test-init-reference.sh
create mode 100755 tools/objtool/tests/generic/test-jump-label-exempt-keys.sh
create mode 100755 tools/objtool/tests/generic/test-jump-label-key.sh
create mode 100755 tools/objtool/tests/generic/test-jump-label-module-key.sh
create mode 100755 tools/objtool/tests/generic/test-jump-label-module-static-key.sh
create mode 100755 tools/objtool/tests/generic/test-jump-label-new-key.sh
create mode 100755 tools/objtool/tests/generic/test-klp-funcs-content.sh
create mode 100755 tools/objtool/tests/generic/test-local-to-global-flip.sh
create mode 100755 tools/objtool/tests/generic/test-local-vs-export.sh
create mode 100755 tools/objtool/tests/generic/test-missing-checksum.sh
create mode 100755 tools/objtool/tests/generic/test-missing-modinfo.sh
create mode 100755 tools/objtool/tests/generic/test-modname-normalize.sh
create mode 100755 tools/objtool/tests/generic/test-module-object.sh
create mode 100755 tools/objtool/tests/generic/test-module-vmlinux-reloc.sh
create mode 100755 tools/objtool/tests/generic/test-new-data.sh
create mode 100755 tools/objtool/tests/generic/test-new-export-ref.sh
create mode 100755 tools/objtool/tests/generic/test-new-function.sh
create mode 100755 tools/objtool/tests/generic/test-post-link.sh
create mode 100755 tools/objtool/tests/generic/test-special-section-shared.sh
create mode 100755 tools/objtool/tests/generic/test-special-section.sh
create mode 100755 tools/objtool/tests/generic/test-static-call-annotate-stripped.sh
create mode 100755 tools/objtool/tests/generic/test-static-call-module-key.sh
create mode 100755 tools/objtool/tests/generic/test-static-call-new.sh
create mode 100755 tools/objtool/tests/generic/test-static-local-uncorrelated.sh
create mode 100755 tools/objtool/tests/generic/test-static-local.sh
create mode 100755 tools/objtool/tests/generic/test-switch-rodata.sh
create mode 100755 tools/objtool/tests/generic/test-symid-discarded.sh
create mode 100755 tools/objtool/tests/generic/test-sympos-vmlinux.sh
create mode 100755 tools/objtool/tests/generic/test-sympos.sh
create mode 100755 tools/objtool/tests/generic/test-symvers-parse-error.sh
create mode 100755 tools/objtool/tests/generic/test-thinlto-ambiguity.sh
create mode 100755 tools/objtool/tests/generic/test-thinlto-local.sh
create mode 100755 tools/objtool/tests/generic/test-ubsan-noise.sh
create mode 100644 tools/objtool/tests/lib.sh
create mode 100755 tools/objtool/tests/run-tests.sh
create mode 100644 tools/objtool/tests/x86/fixtures/alt_annotate.c
create mode 100644 tools/objtool/tests/x86/fixtures/checksum_alt.c
create mode 100644 tools/objtool/tests/x86/fixtures/empty_alternative.c
create mode 100644 tools/objtool/tests/x86/fixtures/kcfi.c
create mode 100644 tools/objtool/tests/x86/fixtures/special_sections.c
create mode 100644 tools/objtool/tests/x86/fixtures/static_call_no_key.c
create mode 100755 tools/objtool/tests/x86/test-alt-annotation.sh
create mode 100755 tools/objtool/tests/x86/test-checksum-alt.sh
create mode 100755 tools/objtool/tests/x86/test-empty-alternative.sh
create mode 100755 tools/objtool/tests/x86/test-kcfi.sh
create mode 100755 tools/objtool/tests/x86/test-manual-klp-static-call.sh
create mode 100755 tools/objtool/tests/x86/test-special-sections.sh
--
2.53.0-Meta
^ permalink raw reply [flat|nested] 77+ messages in thread* [PATCH 01/58] objtool: Add test harness for the klp subcommands 2026-09-11 18:42 [PATCH 00/58] Unit test framework for klp-build toolchain Song Liu @ 2026-09-11 18:42 ` Song Liu 2026-09-11 18:42 ` [PATCH 02/58] objtool/klp: Check the klp test environment once, before any test Song Liu ` (25 subsequent siblings) 26 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:42 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu From: Puranjay Mohan <puranjay@kernel.org> "objtool klp checksum" and "objtool klp diff" work on object files alone, with no kernel, vmlinux or configuration involved, so they can be tested directly. That is worth doing: most klp generation bugs so far have been in symbol correlation, special section extraction and relocation conversion, and several of them failed silently, producing a livepatch which built cleanly but was missing data. A test compiles a fixture twice, as the original and (with -DPATCHED) the patched object, runs both through klp checksum, diffs them and asserts on the result. Fixtures are compiled at test time rather than committed as binaries: codegen varies between compilers and architectures, and that variation is where a good number of these bugs come from. Assertions check properties rather than compare against recorded output. Golden files would need re-recording for every compiler change and would report churn instead of regressions. klp diff resolves symbols against Module.symvers, so the harness writes one. Whether a symbol is listed there decides between an ordinary relocation and a klp relocation, which makes it the main knob tests use. Run with: make -C tools/objtool tests Tests skip when objtool was built without klp support or when a fixture does not build for the target architecture. A missing objtool binary fails instead of skipping, since that means a broken invocation rather than an environment which cannot run the test. Signed-off-by: Puranjay Mohan <puranjay@kernel.org> Co-developed-by: Joe Lawrence <joe.lawrence@redhat.com> Signed-off-by: Joe Lawrence <joe.lawrence@redhat.com> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- tools/objtool/Makefile | 5 +- tools/objtool/tests/fixtures/basic.c | 20 +++ tools/objtool/tests/lib.sh | 175 +++++++++++++++++++++++++++ tools/objtool/tests/run-tests.sh | 20 +++ tools/objtool/tests/test-basic.sh | 17 +++ 5 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 tools/objtool/tests/fixtures/basic.c create mode 100644 tools/objtool/tests/lib.sh create mode 100755 tools/objtool/tests/run-tests.sh create mode 100755 tools/objtool/tests/test-basic.sh diff --git a/tools/objtool/Makefile b/tools/objtool/Makefile index a4484fd22a96..f4ec9f813a20 100644 --- a/tools/objtool/Makefile +++ b/tools/objtool/Makefile @@ -151,6 +151,9 @@ clean: $(LIBSUBCMD)-clean mrproper: clean $(call QUIET_CLEAN, objtool) $(RM) $(OBJTOOL) +tests: $(OBJTOOL) + $(Q)OBJTOOL=$(abspath $(OBJTOOL)) $(srctree)/tools/objtool/tests/run-tests.sh + FORCE: -.PHONY: clean mrproper FORCE +.PHONY: clean mrproper tests FORCE diff --git a/tools/objtool/tests/fixtures/basic.c b/tools/objtool/tests/fixtures/basic.c new file mode 100644 index 000000000000..811529e7bfb1 --- /dev/null +++ b/tools/objtool/tests/fixtures/basic.c @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0 +/* One changed function and one unchanged function. */ + +/* klp diff takes the object's module name from .modinfo */ +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +int untouched(int x) +{ + return x * 3; +} + +int changed(int x) +{ +#ifdef PATCHED + return x + 2; +#else + return x + 1; +#endif +} diff --git a/tools/objtool/tests/lib.sh b/tools/objtool/tests/lib.sh new file mode 100644 index 000000000000..714371232fa3 --- /dev/null +++ b/tools/objtool/tests/lib.sh @@ -0,0 +1,175 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Helpers for the objtool klp tests. A test builds a fixture twice, as the +# original and (with -DPATCHED) the patched object, runs both through +# "klp checksum" and diffs them, then asserts on the result. +# +# Assertions check properties rather than compare against recorded output: +# codegen varies between compilers and golden files would report churn instead +# of regressions. + +TESTS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +FIXTURES_DIR="$TESTS_DIR/fixtures" + +OBJTOOL="${OBJTOOL:-$TESTS_DIR/../objtool}" +CC="${CC:-gcc}" + +# klp-build compiles the kernel this way; klp diff needs per-symbol sections to +# extract individual functions. +FIXTURE_CFLAGS="-c -O2 -ffunction-sections -fdata-sections -fno-asynchronous-unwind-tables" + +test_name="$(basename "$0" .sh)" +workdir= + +pass() { echo "ok - $test_name${1:+: $1}"; exit 0; } +fail() { echo "not ok - $test_name: $1" >&2; exit 1; } +skip() { echo "ok - $test_name # SKIP $1"; exit 0; } + +cleanup() { [ -n "$workdir" ] && rm -rf "$workdir"; } + +# setup [exported symbol...] +setup() +{ + # A relative $OBJTOOL is relative to the objtool directory, not to the + # tests which run from tests/. + [ -x "$OBJTOOL" ] || [ ! -x "$TESTS_DIR/../$OBJTOOL" ] || + OBJTOOL="$TESTS_DIR/../$OBJTOOL" + + # Not finding objtool is a broken invocation, not an environment which + # cannot run the test. Skipping here would read as a pass. + [ -x "$OBJTOOL" ] || fail "objtool not found at '$OBJTOOL', build it first" + + "$OBJTOOL" klp 2>&1 | grep -q checksum || + skip "objtool built without klp support (needs libxxhash)" + command -v "${CC%% *}" >/dev/null || skip "no compiler ($CC)" + + workdir="$(mktemp -d)" || fail "mktemp failed" + trap cleanup EXIT + + export_syms "$@" +} + +# export_syms [symbol...] +# +# Rewrite Module.symvers so exactly these symbols are exported by vmlinux. +# Whether a symbol is listed decides between an ordinary relocation and a klp +# relocation, so tests flip it to cover both. +export_syms() +{ + : > "$workdir/Module.symvers" + for sym in "$@"; do + printf '0x00000000\t%s\tvmlinux\tEXPORT_SYMBOL\t\n' \ + "$sym" >> "$workdir/Module.symvers" + done +} + +# build_pair <fixture.c> [cflags...] +build_pair() +{ + local fixture="$FIXTURES_DIR/$1"; shift + + [ -f "$fixture" ] || fail "missing fixture $fixture" + + $CC $FIXTURE_CFLAGS "$@" -o "$workdir/orig.o" "$fixture" 2>"$workdir/cc.log" || + skip "fixture does not build here: $(tail -1 "$workdir/cc.log")" + $CC $FIXTURE_CFLAGS "$@" -DPATCHED -o "$workdir/patched.o" "$fixture" 2>"$workdir/cc.log" || + skip "fixture does not build here: $(tail -1 "$workdir/cc.log")" +} + +# run_diff [expected exit status] +run_diff() +{ + local expect="${1:-0}" rc=0 + + # Checksums live in the objects, so only generate them once even when a + # test diffs the same pair again with a different Module.symvers. + if [ ! -e "$workdir/.checksummed" ]; then + "$OBJTOOL" klp checksum "$workdir/orig.o" || + fail "klp checksum orig.o failed" + "$OBJTOOL" klp checksum "$workdir/patched.o" || + fail "klp checksum patched.o failed" + touch "$workdir/.checksummed" + fi + + # klp diff looks for Module.symvers relative to the working directory. + ( cd "$workdir" && "$OBJTOOL" klp diff orig.o patched.o out.o ) \ + > "$workdir/diff.log" 2>&1 || rc=$? + + [ "$rc" = "$expect" ] || + fail "klp diff exited $rc, expected $expect: $(tail -2 "$workdir/diff.log")" +} + +cc_supports() +{ + echo 'int f(void) { return 0; }' > "$workdir/flagtest.c" + $CC $1 -c "$workdir/flagtest.c" -o "$workdir/flagtest.o" 2>/dev/null +} + +# partial_link <output> <object...> +# +# "ld -r" through the compiler driver so the link targets the same +# architecture as the objects. +partial_link() +{ + local out="$1"; shift + + $CC -r -nostdlib -o "$out" "$@" 2>/dev/null || + $CC -r -nostdlib -fuse-ld=lld -o "$out" "$@" 2>/dev/null +} + +# find_thinlto_toolchain +# +# Set $THIN_CC and $THIN_LD to a clang and lld from the same LLVM release. A +# mismatched pair fails with "Invalid summary version", which reads like a +# broken test rather than a broken environment. +find_thinlto_toolchain() +{ + local cc ld ver + + for cc in "${THIN_CC:-}" "$CC" clang; do + [ -n "$cc" ] || continue + command -v "${cc%% *}" >/dev/null 2>&1 || continue + + ver=$($cc -dumpversion 2>/dev/null | cut -d. -f1) + + for ld in "${THIN_LD:-}" "ld.lld-$ver" ld.lld; do + [ -n "$ld" ] || continue + command -v "$ld" >/dev/null 2>&1 || continue + + echo 'int probe(void) { return 0; }' > "$workdir/probe.c" + $cc -flto=thin -O2 -c "$workdir/probe.c" \ + -o "$workdir/probe.o" 2>/dev/null || continue + "$ld" -r "$workdir/probe.o" -o "$workdir/probe.elf" \ + 2>/dev/null || continue + + THIN_CC="$cc" + THIN_LD="$ld" + return 0 + done + done + + return 1 +} + +out_sections() { readelf -S -W "$workdir/out.o" 2>/dev/null; } +out_relocs() { readelf -r -W "$workdir/out.o" 2>/dev/null; } +out_symbols() { readelf -s -W "$workdir/out.o" 2>/dev/null; } +diff_log() { cat "$workdir/diff.log"; } + +assert_section() +{ + out_sections | grep -q "[[:space:]]$1[[:space:]]" || + fail "expected section '$1' in output" +} + +assert_patched() +{ + assert_section ".text.$1" +} + +assert_not_patched() +{ + out_sections | grep -q "[[:space:]].text.$1[[:space:]]" && + fail "function '$1' should not have been cloned" + return 0 +} diff --git a/tools/objtool/tests/run-tests.sh b/tools/objtool/tests/run-tests.sh new file mode 100755 index 000000000000..ab1dea58811e --- /dev/null +++ b/tools/objtool/tests/run-tests.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Run the objtool klp tests. Each test-*.sh prints one TAP result line. + +set -u + +cd "$(dirname "$0")" || exit 1 + +tests=( test-*.sh ) +[ "${tests[0]}" = "test-*.sh" ] && { echo "1..0 # SKIP no tests found"; exit 0; } + +echo "1..${#tests[@]}" + +rc=0 +for t in "${tests[@]}"; do + ./"$t" || rc=1 +done + +exit $rc diff --git a/tools/objtool/tests/test-basic.sh b/tools/objtool/tests/test-basic.sh new file mode 100755 index 000000000000..6b769962399a --- /dev/null +++ b/tools/objtool/tests/test-basic.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Only functions whose code changed get cloned into the patch. + +. "$(dirname "$0")/lib.sh" + +setup +build_pair basic.c +run_diff + +assert_patched changed +assert_not_patched untouched +assert_section ".init.klp_funcs" +assert_section ".init.klp_objects" + +pass "changed function cloned, unchanged function left alone" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 02/58] objtool/klp: Check the klp test environment once, before any test 2026-09-11 18:42 [PATCH 00/58] Unit test framework for klp-build toolchain Song Liu 2026-09-11 18:42 ` [PATCH 01/58] objtool: Add test harness for the klp subcommands Song Liu @ 2026-09-11 18:42 ` Song Liu 2026-09-11 19:02 ` sashiko-bot 2026-09-11 18:42 ` [PATCH 03/58] objtool/klp: Group the klp tests by architecture Song Liu ` (24 subsequent siblings) 26 siblings, 1 reply; 77+ messages in thread From: Song Liu @ 2026-09-11 18:42 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu Every test checked for itself that objtool exists, was built with klp support, and that $CC runs. Three problems with that: it is the same work done 40 times, a missing objtool reads as a per-test skip rather than as a suite which cannot run, and a run in which everything skipped still exits 0. Do it once, before any test, in preflight.sh, and record the answers where the tests can read them. If the suite cannot run the whole run fails and says why; a test which gets as far as running can assume its environment. The recorded file is required, not optional: a test which cannot tell where objtool is or which architecture it is on should say so rather than guess at an empty value. It also records which compiler it describes, so a single test run by hand with a different CC after a full run cannot quietly inherit the other one's environment. preflight answers only whether the suite can run at all -- not what the compiler is capable of. A test needing a particular compiler feature probes for it and skips; that costs one compile and keeps the reason next to the test that has to justify it. Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- tools/objtool/Makefile | 3 +- tools/objtool/tests/lib.sh | 114 ++++++++++++++++++++++++++----- tools/objtool/tests/run-tests.sh | 7 ++ 3 files changed, 106 insertions(+), 18 deletions(-) diff --git a/tools/objtool/Makefile b/tools/objtool/Makefile index f4ec9f813a20..2c200d05c276 100644 --- a/tools/objtool/Makefile +++ b/tools/objtool/Makefile @@ -152,7 +152,8 @@ mrproper: clean $(call QUIET_CLEAN, objtool) $(RM) $(OBJTOOL) tests: $(OBJTOOL) - $(Q)OBJTOOL=$(abspath $(OBJTOOL)) $(srctree)/tools/objtool/tests/run-tests.sh + $(Q)OBJTOOL=$(abspath $(OBJTOOL)) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) \ + $(srctree)/tools/objtool/tests/run-tests.sh FORCE: diff --git a/tools/objtool/tests/lib.sh b/tools/objtool/tests/lib.sh index 714371232fa3..6e4e54b10865 100644 --- a/tools/objtool/tests/lib.sh +++ b/tools/objtool/tests/lib.sh @@ -11,8 +11,98 @@ TESTS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" FIXTURES_DIR="$TESTS_DIR/fixtures" +# The kernel's convention: CROSS_COMPILE is the one knob, with per-tool +# overrides for what it does not cover. objtool itself is always a host binary +# -- it is built with HOSTCC and only reads ELF -- so an arm64 machine can run +# the x86 tests against x86 objects given a compiler that emits them. +# +# readelf reads any target, so it rarely needs overriding -- and it must stay +# GNU readelf, whose column layout the assertions parse; llvm-readelf spaces +# them differently. BFD's objcopy is usually built for the host's target +# alone, and llvm-objcopy is the target-agnostic replacement. +CROSS_COMPILE="${CROSS_COMPILE:-}" +CC="${CC:-${CROSS_COMPILE}gcc}" +LD="${LD:-${CROSS_COMPILE}ld}" +READELF="${READELF:-${CROSS_COMPILE}readelf}" +OBJCOPY="${OBJCOPY:-${CROSS_COMPILE}objcopy}" + OBJTOOL="${OBJTOOL:-$TESTS_DIR/../objtool}" -CC="${CC:-gcc}" + +# klp_preflight +# +# Check the environment once, before any test runs, and report what was found. +# +klp_preflight() +{ + local tmp tool cc_version host cc_arch + + bail() { echo "Bail out! $*" >&2; exit 1; } + + # A relative $OBJTOOL is relative to the objtool directory, not tests/. + [ -x "$OBJTOOL" ] || [ ! -x "$TESTS_DIR/../$OBJTOOL" ] || + OBJTOOL="$TESTS_DIR/../$OBJTOOL" + + [ -x "$OBJTOOL" ] || + bail "objtool not found at '$OBJTOOL' -- build it first" + + "$OBJTOOL" klp 2>&1 | grep -q checksum || + bail "objtool was built without klp support; install libxxhash (>= 0.8) and rebuild" + + command -v "${CC%% *}" >/dev/null || bail "compiler not found: $CC" + + for tool in "$READELF" "$OBJCOPY" "$LD"; do + command -v "$tool" >/dev/null || bail "$tool not found" + done + + tmp="$(mktemp -d)" || bail "mktemp failed" + echo 'int probe(void) { return 0; }' > "$tmp/probe.c" + $CC -c -o "$tmp/probe.o" "$tmp/probe.c" 2>/dev/null || + { rm -rf "$tmp"; bail "$CC cannot compile a trivial object"; } + + # $CC, $ARCH and objtool have to agree about the target, and cross runs + # are where they stop agreeing: plain "CC=clang ARCH=x86_64" on an arm64 + # box selects the x86 tests and then builds arm64 objects, because clang + # needs --target= to emit anything but the host's. + # + # Ask objtool rather than comparing machine names. It rejects an object + # it was not built for -- "unexpected ELF machine type" -- so one check + # covers every way the three can disagree, and says so once instead of + # failing every test for the same reason. + "$OBJTOOL" klp checksum "$tmp/probe.o" >/dev/null 2>&1 || + { rm -rf "$tmp" + bail "objtool rejects an object built by '$CC'; they target" \ + "different architectures (set CROSS_COMPILE, or" \ + "--target= for clang)"; } + + # BFD objcopy is usually built for the host's target alone, and + # checksum_of() needs it to read the object under test. + $OBJCOPY -O binary --only-section=.text "$tmp/probe.o" "$tmp/probe.bin" 2>/dev/null || + { rm -rf "$tmp" + bail "$OBJCOPY cannot read objects built by '$CC'; install" \ + "binutils-multiarch or set OBJCOPY=llvm-objcopy"; } + # $ARCH only chooses which directory of tests runs, so it can disagree + # with what $CC builds without objtool noticing -- and the result is the + # wrong set of tests, quietly. + case "$($READELF -hW "$tmp/probe.o" | sed -n 's/.*Machine: *//p')" in + *X86-64*|*Intel*80386*) cc_arch=x86 ;; + *AArch64*) cc_arch=arm64 ;; + *) cc_arch= ;; + esac + rm -rf "$tmp" + + KLP_TEST_PREFLIGHT=done + export OBJTOOL CC KLP_TEST_PREFLIGHT + + cc_version="$($CC --version 2>/dev/null | head -1)" + cat <<EOF +# preflight +# objtool $OBJTOOL (klp: yes) +# compiler $cc_version +# arch $KLP_TEST_ARCH$([ "$arch" = "$host" ] || echo " (host $host, cross)") +EOF +} + +[ -n "${KLP_TEST_PREFLIGHT:-}" ] || klp_preflight # klp-build compiles the kernel this way; klp diff needs per-symbol sections to # extract individual functions. @@ -30,19 +120,9 @@ cleanup() { [ -n "$workdir" ] && rm -rf "$workdir"; } # setup [exported symbol...] setup() { - # A relative $OBJTOOL is relative to the objtool directory, not to the - # tests which run from tests/. - [ -x "$OBJTOOL" ] || [ ! -x "$TESTS_DIR/../$OBJTOOL" ] || - OBJTOOL="$TESTS_DIR/../$OBJTOOL" - - # Not finding objtool is a broken invocation, not an environment which - # cannot run the test. Skipping here would read as a pass. - [ -x "$OBJTOOL" ] || fail "objtool not found at '$OBJTOOL', build it first" - - "$OBJTOOL" klp 2>&1 | grep -q checksum || - skip "objtool built without klp support (needs libxxhash)" - command -v "${CC%% *}" >/dev/null || skip "no compiler ($CC)" - + # The environment was checked once when this file was sourced, so there + # is nothing to verify here: objtool exists at the resolved path, has + # klp support, and $CC works. workdir="$(mktemp -d)" || fail "mktemp failed" trap cleanup EXIT @@ -151,9 +231,9 @@ find_thinlto_toolchain() return 1 } -out_sections() { readelf -S -W "$workdir/out.o" 2>/dev/null; } -out_relocs() { readelf -r -W "$workdir/out.o" 2>/dev/null; } -out_symbols() { readelf -s -W "$workdir/out.o" 2>/dev/null; } +out_sections() { $READELF -S -W "$workdir/out.o" 2>/dev/null; } +out_relocs() { $READELF -r -W "$workdir/out.o" 2>/dev/null; } +out_symbols() { $READELF -s -W "$workdir/out.o" 2>/dev/null; } diff_log() { cat "$workdir/diff.log"; } assert_section() diff --git a/tools/objtool/tests/run-tests.sh b/tools/objtool/tests/run-tests.sh index ab1dea58811e..f6a3e1b7677e 100755 --- a/tools/objtool/tests/run-tests.sh +++ b/tools/objtool/tests/run-tests.sh @@ -2,6 +2,9 @@ # SPDX-License-Identifier: GPL-2.0 # # Run the objtool klp tests. Each test-*.sh prints one TAP result line. +# +# The harness checks the environment once up front and fails the run if the +# suite cannot execute, rather than letting every test skip and exit 0. set -u @@ -10,6 +13,10 @@ cd "$(dirname "$0")" || exit 1 tests=( test-*.sh ) [ "${tests[0]}" = "test-*.sh" ] && { echo "1..0 # SKIP no tests found"; exit 0; } +# Sourcing the harness runs its preflight, and exports what it found so the +# tests inherit it rather than working it out again. +. ./lib.sh + echo "1..${#tests[@]}" rc=0 -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* Re: [PATCH 02/58] objtool/klp: Check the klp test environment once, before any test 2026-09-11 18:42 ` [PATCH 02/58] objtool/klp: Check the klp test environment once, before any test Song Liu @ 2026-09-11 19:02 ` sashiko-bot 0 siblings, 0 replies; 77+ messages in thread From: sashiko-bot @ 2026-09-11 19:02 UTC (permalink / raw) To: Song Liu, live-patching Cc: kernel-team, mbenes, puranjay, jpoimboe, pmladek, peterz, jikos, joe.lawrence Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] Meaningful code and commit message mismatch regarding 'preflight.sh' -- commit 61d3f6be171a4f4b4f3dea4ae2f0323ab1631fcd Author: Song Liu <song@kernel.org> objtool/klp: Check the klp test environment once, before any test This commit centralizes the objtool and compiler environment checks for the klp test suite, running them once upfront rather than in every test. It aims to fail the entire test suite if the environment is unsuitable, speeding up runs and avoiding misleading per-test skips. > Do it once, before any test, in preflight.sh, and record the answers where > the tests can read them. If the suite cannot run the whole run fails and > says why; a test which gets as far as running can assume its environment. > > The recorded file is required, not optional: a test which cannot tell where > objtool is or which architecture it is on should say so rather than guess > at an empty value. [Severity: Medium] Does the implementation match this description? The commit message explicitly states that the checks are done in preflight.sh and that a recorded file is required. However, the patch implements this logic in a bash function instead, and the state is recorded via exported environment variables. > diff --git a/tools/objtool/tests/lib.sh b/tools/objtool/tests/lib.sh > index 714371232fa30..6e4e54b108659 100644 > --- a/tools/objtool/tests/lib.sh > +++ b/tools/objtool/tests/lib.sh [ ... ] > +# klp_preflight > +# > +# Check the environment once, before any test runs, and report what was found. > +# > +klp_preflight() > +{ > + local tmp tool cc_version host cc_arch Is this a stale commit message from a previous iteration, or is there a missing piece of the intended implementation that was supposed to use a file-based approach? -- Sashiko AI review · https://sashiko.dev/#/patchset/20260911185031.1534046-1-song@kernel.org?part=2 ^ permalink raw reply [flat|nested] 77+ messages in thread
* [PATCH 03/58] objtool/klp: Group the klp tests by architecture 2026-09-11 18:42 [PATCH 00/58] Unit test framework for klp-build toolchain Song Liu 2026-09-11 18:42 ` [PATCH 01/58] objtool: Add test harness for the klp subcommands Song Liu 2026-09-11 18:42 ` [PATCH 02/58] objtool/klp: Check the klp test environment once, before any test Song Liu @ 2026-09-11 18:42 ` Song Liu 2026-09-11 18:42 ` [PATCH 04/58] objtool/klp: Classify klp test outcomes Song Liu ` (23 subsequent siblings) 26 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:42 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu Which architecture a test is for is expressed by where it lives: tests are in generic/ or in a directory named for their architecture, each carrying its own fixtures, and the runner executes generic/ plus the one that matches. A test which cannot apply here is then not run at all, rather than running in order to announce that it did not. Layout does this better than a declaration would. There is no x86_only(), and no lookup letting a fixtures/<arch>/ file shadow a generic one of the same name -- an arch-specific test simply carries its own fixtures. Compilers cannot work the same way: CI varies CC over the same tree, so a compiler requirement stays a declaration in the test. What a run leaves out is reported once: # not run: 5 tests in x86/ (this run is arm64) Silence would have been cheaper and wrong. A run covering less than the tree holds must not look like a run that covered all of it. Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../tests/{ => generic}/fixtures/basic.c | 0 .../objtool/tests/{ => generic}/test-basic.sh | 2 +- tools/objtool/tests/lib.sh | 27 ++++++++++++-- tools/objtool/tests/run-tests.sh | 36 ++++++++++++++++--- 4 files changed, 56 insertions(+), 9 deletions(-) rename tools/objtool/tests/{ => generic}/fixtures/basic.c (100%) rename tools/objtool/tests/{ => generic}/test-basic.sh (91%) diff --git a/tools/objtool/tests/fixtures/basic.c b/tools/objtool/tests/generic/fixtures/basic.c similarity index 100% rename from tools/objtool/tests/fixtures/basic.c rename to tools/objtool/tests/generic/fixtures/basic.c diff --git a/tools/objtool/tests/test-basic.sh b/tools/objtool/tests/generic/test-basic.sh similarity index 91% rename from tools/objtool/tests/test-basic.sh rename to tools/objtool/tests/generic/test-basic.sh index 6b769962399a..562edfbc8646 100755 --- a/tools/objtool/tests/test-basic.sh +++ b/tools/objtool/tests/generic/test-basic.sh @@ -3,7 +3,7 @@ # # Only functions whose code changed get cloned into the patch. -. "$(dirname "$0")/lib.sh" +. "$(dirname "$0")/../lib.sh" setup build_pair basic.c diff --git a/tools/objtool/tests/lib.sh b/tools/objtool/tests/lib.sh index 6e4e54b10865..7eb6860cfcad 100644 --- a/tools/objtool/tests/lib.sh +++ b/tools/objtool/tests/lib.sh @@ -9,7 +9,10 @@ # of regressions. TESTS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -FIXTURES_DIR="$TESTS_DIR/fixtures" + +# Tests live in generic/ or in an architecture directory beside it, and each +# carries its own fixtures. +FIXTURES_DIR="$(cd "$(dirname "$0")/fixtures" 2>/dev/null && pwd)" # The kernel's convention: CROSS_COMPILE is the one knob, with per-tool # overrides for what it does not cover. objtool itself is always a host binary @@ -34,7 +37,7 @@ OBJTOOL="${OBJTOOL:-$TESTS_DIR/../objtool}" # klp_preflight() { - local tmp tool cc_version host cc_arch + local tmp tool cc_version arch host cc_arch bail() { echo "Bail out! $*" >&2; exit 1; } @@ -90,8 +93,26 @@ klp_preflight() esac rm -rf "$tmp" + # Normalize to the kernel's SRCARCH. + case "${ARCH:-$(uname -m)}" in + x86_64|i?86) arch=x86 ;; + aarch64*) arch=arm64 ;; + *) arch="${ARCH:-$(uname -m)}" ;; + esac + + case "$(uname -m)" in + x86_64|i?86) host=x86 ;; + aarch64*) host=arm64 ;; + *) host="$(uname -m)" ;; + esac + + [ -z "$cc_arch" ] || [ "$cc_arch" = "$arch" ] || + bail "ARCH says $arch but '$CC' builds $cc_arch objects;" \ + "the $arch tests would run against the wrong architecture" + + KLP_TEST_ARCH="$arch" KLP_TEST_PREFLIGHT=done - export OBJTOOL CC KLP_TEST_PREFLIGHT + export OBJTOOL CC KLP_TEST_ARCH KLP_TEST_PREFLIGHT cc_version="$($CC --version 2>/dev/null | head -1)" cat <<EOF diff --git a/tools/objtool/tests/run-tests.sh b/tools/objtool/tests/run-tests.sh index f6a3e1b7677e..48728a98d995 100755 --- a/tools/objtool/tests/run-tests.sh +++ b/tools/objtool/tests/run-tests.sh @@ -3,6 +3,11 @@ # # Run the objtool klp tests. Each test-*.sh prints one TAP result line. # +# Tests live in generic/ and in a directory per architecture. A run executes +# generic/ plus the one matching this architecture, so a test which cannot +# apply here is not run rather than reporting a skip; what was left out is +# reported once, as a comment, so differing coverage is still visible. +# # The harness checks the environment once up front and fails the run if the # suite cannot execute, rather than letting every test skip and exit 0. @@ -10,13 +15,34 @@ set -u cd "$(dirname "$0")" || exit 1 -tests=( test-*.sh ) -[ "${tests[0]}" = "test-*.sh" ] && { echo "1..0 # SKIP no tests found"; exit 0; } - -# Sourcing the harness runs its preflight, and exports what it found so the -# tests inherit it rather than working it out again. +# Sourcing the harness runs its preflight, which decides which architecture +# this run is for -- so the test list cannot be built before it has, and the +# tests inherit the answers rather than working them out again. . ./lib.sh +dirs=( generic ) +[ -d "$KLP_TEST_ARCH" ] && dirs+=( "$KLP_TEST_ARCH" ) + +tests=() +for d in "${dirs[@]}"; do + for t in "$d"/test-*.sh; do + [ -f "$t" ] && tests+=( "$t" ) + done +done +[ "${#tests[@]}" -gt 0 ] || { echo "1..0 # SKIP no tests found"; exit 0; } + +# Tests for another architecture are absent from this run entirely. Say how +# many, so a run which covers less than the tree holds does not look like one +# that covers all of it. +for d in */; do + d="${d%/}" + case "$d" in generic|"$KLP_TEST_ARCH") continue ;; esac + n=$(ls "$d"/test-*.sh 2>/dev/null | wc -l) + [ "$n" -gt 0 ] || continue + echo "# not run: $n test$( [ "$n" = 1 ] || echo s ) in $d/" \ + "(this run is $KLP_TEST_ARCH)" +done + echo "1..${#tests[@]}" rc=0 -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 04/58] objtool/klp: Classify klp test outcomes 2026-09-11 18:42 [PATCH 00/58] Unit test framework for klp-build toolchain Song Liu ` (2 preceding siblings ...) 2026-09-11 18:42 ` [PATCH 03/58] objtool/klp: Group the klp tests by architecture Song Liu @ 2026-09-11 18:42 ` Song Liu 2026-09-11 18:42 ` [PATCH 05/58] objtool/klp: Build klp test fixtures through the harness Song Liu ` (22 subsequent siblings) 26 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:42 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu A test could pass, fail, or skip, and the runner reported only the exit status of the last one to fail. Skips are the interesting case: they are how a suite quietly stops testing anything. Three kinds, and the runner counts them separately: declared the test said in advance it does not apply here, e.g. gcc_only on a clang run. Expected indefinitely. probe the construct did not turn up in the built object this time. Weaker: one which becomes permanent is a fixture that stopped testing anything. undeclared counted as a failure. A test which gives up for a reason it never declared is a hole, not an outcome. xfail and xpass come with them, so a known failure is reported rather than commented out and forgotten, and one which starts passing says so instead of going quietly green. The result line is what gets classified, not the whole of a test's output: objtool warns on stderr and the runner captures it, and a stray line ahead of the result would otherwise leave the exit status to decide -- counting an expected failure, which exits 0, as a pass. Every test owes the plan exactly one result line, so a substituted verdict replaces the test's own rather than being printed beside it, and a test which dies before reporting gets a result of its own instead of being inferred from its exit status. Also: a TAP version line, LC_ALL=C so the order tests run in and the behaviour of grep's character ranges inside them do not depend on the invoking shell's locale, and fail() on stdout with every other verdict. Co-developed-by: Puranjay Mohan <puranjay@kernel.org> Signed-off-by: Puranjay Mohan <puranjay@kernel.org> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- tools/objtool/tests/lib.sh | 67 ++++++++++++-- tools/objtool/tests/run-tests.sh | 148 ++++++++++++++++++++++++++----- 2 files changed, 186 insertions(+), 29 deletions(-) diff --git a/tools/objtool/tests/lib.sh b/tools/objtool/tests/lib.sh index 7eb6860cfcad..7185198253bb 100644 --- a/tools/objtool/tests/lib.sh +++ b/tools/objtool/tests/lib.sh @@ -133,8 +133,38 @@ test_name="$(basename "$0" .sh)" workdir= pass() { echo "ok - $test_name${1:+: $1}"; exit 0; } -fail() { echo "not ok - $test_name: $1" >&2; exit 1; } -skip() { echo "ok - $test_name # SKIP $1"; exit 0; } +fail() { echo "not ok - $test_name: $1"; exit 1; } + +# Two kinds of skip, and the runner tells them apart. +# +# declared_skip the test said in advance it does not apply here, e.g. +# gcc_only on a clang run. Expected indefinitely. +# probe_skip the construct did not turn up in the built object this +# time. Weaker: it may appear on another compiler version, +# and one which becomes permanent is a fixture that quietly +# stopped testing anything. +# +# A bare skip() is neither, and the runner counts it as a failure: a test which +# gives up for a reason it never declared is a hole, not an outcome. +declared_skip() { echo "ok - $test_name # SKIP (declared) $1"; exit 0; } +probe_skip() { echo "ok - $test_name # SKIP (probe) $1"; exit 0; } +skip() { echo "ok - $test_name # SKIP $1"; exit 0; } + +# TAP directives. A test which is known to fail reports it rather than being +# commented out and forgotten, and one which starts passing again says so +# instead of quietly going green: the expectation has to be removed by hand, +# which is the point. +xfail() +{ + echo "not ok - $test_name${1:+: $1} # TODO known failure" + exit 0 +} + +xpass() +{ + echo "ok - $test_name${1:+: $1} # TODO expected failure, but passed" + exit 1 +} cleanup() { [ -n "$workdir" ] && rm -rf "$workdir"; } @@ -164,6 +194,23 @@ export_syms() done } +# gcc_only / clang_only <reason> +gcc_only() +{ + case "$($CC --version 2>/dev/null | head -1)" in + *[Gg][Cc][Cc]*) return 0 ;; + esac + declared_skip "gcc only${1:+: $1}" +} + +clang_only() +{ + case "$($CC --version 2>/dev/null | head -1)" in + *clang*) return 0 ;; + esac + declared_skip "clang only${1:+: $1}" +} + # build_pair <fixture.c> [cflags...] build_pair() { @@ -172,9 +219,9 @@ build_pair() [ -f "$fixture" ] || fail "missing fixture $fixture" $CC $FIXTURE_CFLAGS "$@" -o "$workdir/orig.o" "$fixture" 2>"$workdir/cc.log" || - skip "fixture does not build here: $(tail -1 "$workdir/cc.log")" + probe_skip "fixture does not build here: $(tail -1 "$workdir/cc.log")" $CC $FIXTURE_CFLAGS "$@" -DPATCHED -o "$workdir/patched.o" "$fixture" 2>"$workdir/cc.log" || - skip "fixture does not build here: $(tail -1 "$workdir/cc.log")" + probe_skip "fixture does not build here: $(tail -1 "$workdir/cc.log")" } # run_diff [expected exit status] @@ -220,16 +267,20 @@ partial_link() # find_thinlto_toolchain # -# Set $THIN_CC and $THIN_LD to a clang and lld from the same LLVM release. A +# Set $THIN_LD to an lld from the same LLVM release as $CC (or THIN_CC). A # mismatched pair fails with "Invalid summary version", which reads like a # broken test rather than a broken environment. +# +# ThinLTO is clang-only; callers must use clang_only before calling this. +# Only $CC (or an explicit THIN_CC override) is consulted -- the harness does +# not search for a second compiler beside a gcc $CC. find_thinlto_toolchain() { - local cc ld ver + local cc ver ld - for cc in "${THIN_CC:-}" "$CC" clang; do + for cc in "${THIN_CC:-}" "$CC"; do [ -n "$cc" ] || continue - command -v "${cc%% *}" >/dev/null 2>&1 || continue + command -v "${cc%% *}" >/dev/null 2>&1 || return 1 ver=$($cc -dumpversion 2>/dev/null | cut -d. -f1) diff --git a/tools/objtool/tests/run-tests.sh b/tools/objtool/tests/run-tests.sh index 48728a98d995..6b937fc7f5bd 100755 --- a/tools/objtool/tests/run-tests.sh +++ b/tools/objtool/tests/run-tests.sh @@ -8,13 +8,51 @@ # apply here is not run rather than reporting a skip; what was left out is # reported once, as a comment, so differing coverage is still visible. # +# A run covers one compiler and one architecture; CI runs the combinations. # The harness checks the environment once up front and fails the run if the # suite cannot execute, rather than letting every test skip and exit 0. set -u +# Determinism: the order test-*.sh expands in, and how grep's character ranges +# and sort's collation behave inside the tests, are all locale-dependent. A +# suite whose results depend on the invoking shell's locale is a suite whose +# failures cannot be reproduced. +export LC_ALL=C + +usage() +{ + cat <<EOF +usage: $(basename "$0") [test...] + +Run the objtool klp tests for this architecture: everything in generic/, plus +everything in the directory named for it. With no arguments, runs all of them. +A test may be named with or without its "test-" prefix and ".sh" suffix, and is +looked for in both directories. + +Environment: + OBJTOOL objtool binary to test (default ../objtool) + CC compiler used to build fixtures (default gcc) + ARCH architecture the tests are for (default: uname -m) + +A test which needs something of its own says so in its skip message. +EOF + exit "${1:-0}" +} + cd "$(dirname "$0")" || exit 1 +while [ $# -gt 0 ]; do + case "$1" in + -h|--help) usage ;; + --) shift; break ;; + -*) echo "unknown option: $1" >&2; usage 1 ;; + *) break ;; + esac +done + +echo "TAP version 13" + # Sourcing the harness runs its preflight, which decides which architecture # this run is for -- so the test list cannot be built before it has, and the # tests inherit the answers rather than working them out again. @@ -23,31 +61,99 @@ cd "$(dirname "$0")" || exit 1 dirs=( generic ) [ -d "$KLP_TEST_ARCH" ] && dirs+=( "$KLP_TEST_ARCH" ) -tests=() -for d in "${dirs[@]}"; do - for t in "$d"/test-*.sh; do - [ -f "$t" ] && tests+=( "$t" ) +if [ $# -gt 0 ]; then + tests=() + for arg in "$@"; do + name="test-${arg#test-}"; name="${name%.sh}.sh" + found= + for d in "${dirs[@]}"; do + [ -f "$d/$name" ] || continue + [ -x "$d/$name" ] || + { echo "not executable: $d/$name" >&2; exit 1; } + tests+=( "$d/$name" ); found=y + done + [ -n "$found" ] || + { echo "no such test for $KLP_TEST_ARCH: $arg" >&2; exit 1; } done -done -[ "${#tests[@]}" -gt 0 ] || { echo "1..0 # SKIP no tests found"; exit 0; } - -# Tests for another architecture are absent from this run entirely. Say how -# many, so a run which covers less than the tree holds does not look like one -# that covers all of it. -for d in */; do - d="${d%/}" - case "$d" in generic|"$KLP_TEST_ARCH") continue ;; esac - n=$(ls "$d"/test-*.sh 2>/dev/null | wc -l) - [ "$n" -gt 0 ] || continue - echo "# not run: $n test$( [ "$n" = 1 ] || echo s ) in $d/" \ - "(this run is $KLP_TEST_ARCH)" -done +else + tests=() + for d in "${dirs[@]}"; do + for t in "$d"/test-*.sh; do + [ -f "$t" ] && tests+=( "$t" ) + done + done + [ "${#tests[@]}" -gt 0 ] || + { echo "1..0 # SKIP no tests found"; exit 0; } + + # Tests for another architecture are absent from this run entirely. Say + # how many, so a run which covers less than the tree holds does not look + # like one that covers all of it. + for d in */; do + d="${d%/}" + case "$d" in generic|"$KLP_TEST_ARCH") continue ;; esac + n=$(ls "$d"/test-*.sh 2>/dev/null | wc -l) + [ "$n" -gt 0 ] || continue + echo "# not run: $n test$( [ "$n" = 1 ] || echo s ) in $d/" \ + "(this run is $KLP_TEST_ARCH)" + done +fi echo "1..${#tests[@]}" -rc=0 +pass=0 fail=0 static_skip=0 probe_skip=0 xfail=0 xpass=0 + for t in "${tests[@]}"; do - ./"$t" || rc=1 + out="$(./"$t" 2>&1)" + rc=$? + + # A test prints one result line, but it is not necessarily the only + # thing it prints: objtool warns on stderr, and the runner captures + # that. Classify the result line itself rather than the whole of the + # output, or a stray line ahead of it makes every pattern below miss and + # the exit status decide -- which would count an expected failure, which + # exits 0, as a pass. + result="$(printf '%s\n' "$out" | grep -E '^(ok|not ok)' | tail -1)" + rest="$(printf '%s\n' "$out" | grep -Ev '^(ok|not ok)')" + + # Classify from the result line, not the exit status: a skip and a pass + # both exit 0, and telling them apart is the point of counting. + # + # The two skip kinds differ in what they promise. A static skip was + # declared before the test ran ("clang does not do this"), so it is + # expected indefinitely. A probe skip means the construct did not turn + # up this time, which is weaker and worth watching: one that becomes + # permanent is a fixture that quietly stopped testing anything. + case "$result" in + *"# SKIP (declared)"*) static_skip=$((static_skip + 1)) ;; + *"# SKIP (probe)"*) probe_skip=$((probe_skip + 1)) ;; + *"# SKIP"*) + # An undeclared skip: the test gave up for a reason it never + # said it might. That is a hole, not an expected outcome. + # + # Replace the line rather than adding one. Every test owes the + # plan exactly one result, and a consumer counting them is + # entitled to say so when the totals disagree. + rest="$rest${rest:+$'\n'}was: $result" + result="not ok - $(basename "$t" .sh): undeclared skip" + result="$result (use gcc_only/clang_only or require_input_*)" + fail=$((fail + 1)) ;; + "not ok"*"# TODO"*) xfail=$((xfail + 1)) ;; + "ok"*"# TODO"*) xpass=$((xpass + 1)) ;; + "not ok"*) fail=$((fail + 1)) ;; + "ok"*) pass=$((pass + 1)) ;; + *) + # No result line at all: the test died before reporting. + rest="$rest${rest:+$'\n'}exited $rc without a result line" + result="not ok - $(basename "$t" .sh): no TAP result" + fail=$((fail + 1)) ;; + esac + + echo "$result" + [ -n "$rest" ] && printf '%s\n' "$rest" | sed 's/^[^#]/# &/' + done -exit $rc +echo "# pass:$pass fail:$fail static-skip:$static_skip" \ + "probe-skip:$probe_skip xfail:$xfail xpass:$xpass" + +[ "$fail" = 0 ] && [ "$xpass" = 0 ] -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 05/58] objtool/klp: Build klp test fixtures through the harness 2026-09-11 18:42 [PATCH 00/58] Unit test framework for klp-build toolchain Song Liu ` (3 preceding siblings ...) 2026-09-11 18:42 ` [PATCH 04/58] objtool/klp: Classify klp test outcomes Song Liu @ 2026-09-11 18:42 ` Song Liu 2026-09-11 18:42 ` [PATCH 06/58] objtool/klp: Grow the klp test harness vocabulary Song Liu ` (21 subsequent siblings) 26 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:42 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu Compiling a fixture is the one thing every test does, and it has more invariants than it looks. - The flags describe the kernel a fixture stands in for, and each is written down with its reason. That substitution is the whole mechanism by which these tests cover configurations without building a kernel -- objtool reads no .config, so a configuration reaches it only as compiler flags and the bytes they produce -- and a flag with no stated motive is indistinguishable from a mistake. - -c is not one of them. Producing an object rather than a program is the helper's contract, not something a test may reconsider, so it lives at the compile and an override cannot drop it. - A fixture which will not compile is a failure, not a skip. It is far more often a mistake in the fixture than a compiler which cannot express the construct, and the two are indistinguishable once reported as a skip. - A newly produced object has no checksums in it, so producing one invalidates the record that checksumming has already been done. Left to itself that invariant ends up in each test which rebuilds. build_one handles a single object for the tests which need more than two or different names; build_pair and build_module_pair are expressed in terms of it, so none of the above can be lost by going around them. run_checksum comes out of run_diff for the same reason: a test which wants checksums without a diff should not have to run one. Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- tools/objtool/tests/lib.sh | 175 +++++++++++++++++++++++++++++++++---- 1 file changed, 156 insertions(+), 19 deletions(-) diff --git a/tools/objtool/tests/lib.sh b/tools/objtool/tests/lib.sh index 7185198253bb..0a9da178f4dc 100644 --- a/tools/objtool/tests/lib.sh +++ b/tools/objtool/tests/lib.sh @@ -125,13 +125,26 @@ EOF [ -n "${KLP_TEST_PREFLIGHT:-}" ] || klp_preflight -# klp-build compiles the kernel this way; klp diff needs per-symbol sections to -# extract individual functions. -FIXTURE_CFLAGS="-c -O2 -ffunction-sections -fdata-sections -fno-asynchronous-unwind-tables" +# What every fixture is built with. These describe the kernel a fixture stands +# in for; -c is build_one's contract rather than a property of that kernel, so +# it lives at the compile where an override cannot drop it. +# +# -O2 the kernel's default +# -ffunction-sections -fdata-sections klp-build passes these itself, through +# KCFLAGS, whatever the configuration +# -fno-asynchronous-unwind-tables arch/x86/Makefile sets this always, so +# kernel objects carry no .eh_frame +# +# A test overrides it; see tools/objtool/Documentation/klp-write-tests.txt. +FIXTURE_CFLAGS="-O2 -ffunction-sections -fdata-sections \ + -fno-asynchronous-unwind-tables" test_name="$(basename "$0" .sh)" workdir= +orig_obj=orig.o +patched_obj=patched.o + pass() { echo "ok - $test_name${1:+: $1}"; exit 0; } fail() { echo "not ok - $test_name: $1"; exit 1; } @@ -211,17 +224,64 @@ clang_only() declared_skip "clang only${1:+: $1}" } +# build_one <fixture.c> <output object> [cflags...] +build_one() +{ + local fixture out + fixture="$FIXTURES_DIR/$1" + out="$workdir/$2" + shift 2 + + [ -f "$fixture" ] || fail "missing fixture $fixture" + + # run_checksum only runs once per workdir. A fresh object has no + # checksums in it, so anything built now needs that to happen again. + rm -f "$workdir/.checksummed" + + $CC -c $FIXTURE_CFLAGS "$@" -o "$out" "$fixture" 2>"$workdir/cc.log" || + fail "$(basename "$fixture") does not build: $(tail -1 "$workdir/cc.log")" +} + # build_pair <fixture.c> [cflags...] build_pair() { - local fixture="$FIXTURES_DIR/$1"; shift + local fixture="$1"; shift - [ -f "$fixture" ] || fail "missing fixture $fixture" + build_one "$fixture" orig.o "$@" + build_one "$fixture" patched.o "$@" -DPATCHED +} - $CC $FIXTURE_CFLAGS "$@" -o "$workdir/orig.o" "$fixture" 2>"$workdir/cc.log" || - probe_skip "fixture does not build here: $(tail -1 "$workdir/cc.log")" - $CC $FIXTURE_CFLAGS "$@" -DPATCHED -o "$workdir/patched.o" "$fixture" 2>"$workdir/cc.log" || - probe_skip "fixture does not build here: $(tail -1 "$workdir/cc.log")" +# run_objtool_check <objtool arguments...> +# +# Run objtool's ordinary check pass over the pair, as the kernel build does. +# +# Some of what klp diff consumes is produced by this pass rather than by the +# compiler: .static_call_sites, .mcount_loc, .ibt_endbr_seal, ORC. +# +# Only module objects see it before klp-build -- with CONFIG_KLP_BUILD the +# per-object pass is deferred, so built-in objects reach klp diff exactly as +# the compiler left them. +run_objtool_check() +{ + local obj + + for obj in "$orig_obj" "$patched_obj"; do + "$OBJTOOL" "$@" "$workdir/$obj" || + fail "objtool $* failed on $obj" + done +} + +run_checksum() +{ + # Checksums live in the objects, so only generate them once even when a + # test diffs the same pair again with a different Module.symvers. + [ -e "$workdir/.checksummed" ] && return 0 + + "$OBJTOOL" klp checksum "$workdir/$orig_obj" || + fail "klp checksum $orig_obj failed" + "$OBJTOOL" klp checksum "$workdir/$patched_obj" || + fail "klp checksum $patched_obj failed" + touch "$workdir/.checksummed" } # run_diff [expected exit status] @@ -229,18 +289,10 @@ run_diff() { local expect="${1:-0}" rc=0 - # Checksums live in the objects, so only generate them once even when a - # test diffs the same pair again with a different Module.symvers. - if [ ! -e "$workdir/.checksummed" ]; then - "$OBJTOOL" klp checksum "$workdir/orig.o" || - fail "klp checksum orig.o failed" - "$OBJTOOL" klp checksum "$workdir/patched.o" || - fail "klp checksum patched.o failed" - touch "$workdir/.checksummed" - fi + run_checksum # klp diff looks for Module.symvers relative to the working directory. - ( cd "$workdir" && "$OBJTOOL" klp diff orig.o patched.o out.o ) \ + ( cd "$workdir" && "$OBJTOOL" klp diff "$orig_obj" "$patched_obj" out.o ) \ > "$workdir/diff.log" 2>&1 || rc=$? [ "$rc" = "$expect" ] || @@ -261,10 +313,95 @@ partial_link() { local out="$1"; shift + rm -f "$workdir/.checksummed" + $CC -r -nostdlib -o "$out" "$@" 2>/dev/null || $CC -r -nostdlib -fuse-ld=lld -o "$out" "$@" 2>/dev/null } +# link_vmlinux <output> <object...> +# +# Link objects into an executable, the way the kernel's final link produces +# vmlinux from vmlinux.o. Entry point 0 and no libc: nothing runs it, it only +# has to be a linked image with resolved addresses. +# +# The sub-sections have to come out in name order rather than object order, +# the way the kernel's linker script gathers .text.unlikely and .data.. apart +# from the rest. That reordering is the entire reason .klp.symid exists: a +# link which preserves order cannot tell a correct sympos from one that merely +# counted, and the caller checks the two orders really did diverge. +# +# A linker script rather than --sort-section=name, because lld accepts that +# option and ignores it -- so on a host where only lld can link the target, the +# test would quietly stop testing the thing it is named for. +# +# Three attempts because a cross run has neither $LD nor the compiler's default +# linker able to touch the target: on an arm64 host linking x86 objects, only +# lld will do it. +link_vmlinux() +{ + local out="$1" lds="$workdir/sort.lds"; shift + + echo 'SECTIONS { .data : { *(SORT_BY_NAME(.data.*)) } }' > "$lds" + + $LD -e 0 -T "$lds" -o "$out" "$@" 2>/dev/null || + $CC -nostdlib -Wl,-e,0 -Wl,-T,"$lds" \ + -o "$out" "$@" 2>/dev/null || + $CC -nostdlib -fuse-ld=lld -Wl,-e,0 -Wl,-T,"$lds" \ + -o "$out" "$@" 2>/dev/null +} + +# make_vmlinux_pair <orig object...> -- <patched object...> +# +# Build the vmlinux.o / vmlinux pair klp diff needs to resolve sympos the way +# it does for built-in code, and point the diff at it. +# +# For a module, sympos is a count in symbol table order, which klp diff can do +# from the object alone. vmlinux is different: the final link reorders +# sub-sections, so the position comes from the linked image, bridged by +# .klp.symid. klp diff only looks for that when the object it was handed is +# called vmlinux.o and a vmlinux sits beside it -- so both the name and the +# linked image matter. +make_vmlinux_pair() +{ + local orig=() patched=() seen= arg + + for arg in "$@"; do + if [ "$arg" = -- ]; then seen=y; continue; fi + if [ -n "$seen" ]; then patched+=( "$arg" ); else orig+=( "$arg" ); fi + done + + partial_link "$workdir/vmlinux.o" "${orig[@]}" || + probe_skip "partial link unavailable" + partial_link "$workdir/patched.o" "${patched[@]}" || + probe_skip "partial link unavailable" + + "$OBJTOOL" --klp-symids --link "$workdir/vmlinux.o" || + fail "objtool --klp-symids failed" + + link_vmlinux "$workdir/vmlinux" "$workdir/vmlinux.o" || + probe_skip "cannot link a vmlinux here" + + orig_obj=vmlinux.o +} + +# build_module_pair <fixture.c> <module name> [cflags...] +# +# Build the pair as objects belonging to a module rather than to vmlinux. klp +# diff reads the object's module name from .modinfo, and that decides which +# object a relocation is attributed to and whether a reference counts as +# cross-module, so a good deal of the code has a module path the vmlinux +# fixtures never reach. +# +# The fixture defines its .modinfo name from MODNAME. Passing that through +# -D needs two levels of quoting, which is easy to get wrong at the call site. +build_module_pair() +{ + local fixture="$1" modname="$2"; shift 2 + + build_pair "$fixture" -DMODNAME="\"$modname\"" "$@" +} + # find_thinlto_toolchain # # Set $THIN_LD to an lld from the same LLVM release as $CC (or THIN_CC). A -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 06/58] objtool/klp: Grow the klp test harness vocabulary 2026-09-11 18:42 [PATCH 00/58] Unit test framework for klp-build toolchain Song Liu ` (4 preceding siblings ...) 2026-09-11 18:42 ` [PATCH 05/58] objtool/klp: Build klp test fixtures through the harness Song Liu @ 2026-09-11 18:42 ` Song Liu 2026-09-11 19:03 ` sashiko-bot 2026-09-11 18:42 ` [PATCH 07/58] objtool/klp: Give each run one working directory, one per test inside it Song Liu ` (20 subsequent siblings) 26 siblings, 1 reply; 77+ messages in thread From: Song Liu @ 2026-09-11 18:42 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu Assertions for what klp diff produces, and for what went into it. The output side covers the things a livepatch is made of: cloned sections and symbols, klp symbols and their sympos, tombstones, relocations by section and by count, the converted .klp.rela sections and their SHF_RELA_LIVEPATCH flag, SHN_LIVEPATCH symbols, recorded checksums, and klp diff's own diagnostics -- a rejection for the wrong reason is not a pass. The input side matters just as much, and is easier to forget. A test which asserts only on the output passes when the compiler never emitted the construct under test, and reads as coverage it does not have. So there are two forms, and the difference between them is a statement about why the thing might be absent: require_* the compiler cannot produce it here -> skip assert_* the fixture is supposed to produce it -> fail Names are quoted before they reach grep. Nearly every name here contains dots -- .text.target, .klp.rela.vmlinux -- and an unescaped dot matches any character, so an assertion for one section could be satisfied by a different one whose name merely lines up. Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- tools/objtool/tests/lib.sh | 360 ++++++++++++++++++++++++++++++++++++- 1 file changed, 356 insertions(+), 4 deletions(-) diff --git a/tools/objtool/tests/lib.sh b/tools/objtool/tests/lib.sh index 0a9da178f4dc..a4d126e6c9cb 100644 --- a/tools/objtool/tests/lib.sh +++ b/tools/objtool/tests/lib.sh @@ -201,9 +201,42 @@ setup() export_syms() { : > "$workdir/Module.symvers" + add_exports vmlinux "$@" +} + +# add_exports <object> [symbol...] +# +# Append exports owned by one object, without clearing what is already there, +# so a test can describe a kernel where several objects export things. +# +# Which object owns a symbol is not cosmetic: a reference to a vmlinux symbol +# is applied when the patch module loads, and a reference to a module's symbol +# when that patched module loads, so klp diff files them in different sections. +add_exports() +{ + local owner="$1"; shift + + add_exports_ns "$owner" "" "$@" +} + +# add_exports_ns <object> <namespace> [symbol...] +# +# Exports in a symbol namespace, the last field of a Module.symvers line. +# +# A "module:<names>" namespace is EXPORT_SYMBOL_FOR_MODULES(), where the module +# loader grants access by matching the importing module's name against the +# list. A livepatch module is never on that list, so such a symbol has to be +# referenced the way an unexported one is. Ordinary namespaces are not +# special here. +add_exports_ns() +{ + local owner="$1" ns="$2"; shift 2 + + local sym + for sym in "$@"; do - printf '0x00000000\t%s\tvmlinux\tEXPORT_SYMBOL\t\n' \ - "$sym" >> "$workdir/Module.symvers" + printf '0x00000000\t%s\t%s\tEXPORT_SYMBOL\t%s\n' \ + "$sym" "$owner" "$ns" >> "$workdir/Module.symvers" done } @@ -445,12 +478,87 @@ out_relocs() { $READELF -r -W "$workdir/out.o" 2>/dev/null; } out_symbols() { $READELF -s -W "$workdir/out.o" 2>/dev/null; } diff_log() { cat "$workdir/diff.log"; } +# out_strings <section> +# +# The strings in one section of the output, for the names livepatch matches on. +out_strings() { $READELF -p "$1" "$workdir/out.o" 2>/dev/null; } + +# Checks on the input objects, to run before klp diff. The two forms differ in +# what an absent construct means: +# +# require_* the compiler cannot produce it here -> skip +# assert_* the fixture is supposed to produce it -> fail + +in_sections() { $READELF -S -W "$workdir/$1" 2>/dev/null; } +in_symbols() { $READELF -s -W "$workdir/$1" 2>/dev/null; } +in_relocs() { $READELF -r -W "$workdir/$1" 2>/dev/null; } + +# count_input_symbols <object> <name> +# +# How many object symbols of exactly that name the input has. Deliberately not +# a grep: readelf lists section symbols too, and a newer binutils prints their +# name -- ".data.<name>" -- where an older one leaves the column blank. A dot +# is not a word character, so "grep -w <name>" counts that line as well, and +# the same object gives a different answer depending on which readelf reads it. +count_input_symbols() +{ + in_symbols "$1" | awk -v n="$2" '$4 == "OBJECT" && $8 == n' | wc -l +} + +# re_quote <string> +# +# A string as a literal basic regular expression. Nearly every name these +# assertions match on contains a dot -- .text.target, .klp.rela.vmlinux -- and +# an unescaped dot matches any character, so an assertion for one section can be +# satisfied by a different one whose name merely lines up. +re_quote() { printf '%s' "$1" | sed 's/[].[^$*\\/]/\\&/g'; } + +has_input_section() { in_sections "$1" | grep -q "[[:space:]]$(re_quote "$2")[[:space:]]"; } +has_input_symbol() { in_symbols "$1" | grep -qw -- "$2"; } + +assert_input_section() +{ + local obj + + for obj in "$orig_obj" "$patched_obj"; do + has_input_section "$obj" "$1" || + fail "fixture produced no section '$1' in $obj" + done +} + +assert_input_symbol() +{ + local obj + + for obj in "$orig_obj" "$patched_obj"; do + has_input_symbol "$obj" "$1" || + fail "fixture produced no symbol '$1' in $obj" + done +} + +require_input_section() +{ + local obj + + for obj in "$orig_obj" "$patched_obj"; do + has_input_section "$obj" "$1" || + probe_skip "compiler produced no section '$1' here" + done +} + assert_section() { - out_sections | grep -q "[[:space:]]$1[[:space:]]" || + out_sections | grep -q "[[:space:]]$(re_quote "$1")[[:space:]]" || fail "expected section '$1' in output" } +assert_no_section() +{ + out_sections | grep -q "[[:space:]]$(re_quote "$1")[[:space:]]" && + fail "unexpected section '$1' in output" + return 0 +} + assert_patched() { assert_section ".text.$1" @@ -458,7 +566,251 @@ assert_patched() assert_not_patched() { - out_sections | grep -q "[[:space:]].text.$1[[:space:]]" && + out_sections | grep -q "[[:space:]]$(re_quote ".text.$1")[[:space:]]" && fail "function '$1' should not have been cloned" return 0 } + +# section_relocs <section> +# +# The relocations against one section. readelf prints every relocation section +# in turn, so a test asking about ".smp_locks" has to cut its block out of the +# listing first. +section_relocs() +{ + local sec="${1//./\\.}" + + out_relocs | awk "/rela$sec/,/^\$/" +} + +assert_reloc_sym() +{ + section_relocs "$1" | grep -qw -- "$2" || + fail "expected a relocation to '$2' in '$1'" +} + +assert_no_reloc_sym() +{ + section_relocs "$1" | grep -qw -- "$2" && + fail "unexpected relocation to '$2' in '$1'" + return 0 +} + +# assert_reloc_count <section> <count> +# +# Counts relocation entries, not header or blank lines: whether a special +# section entry was extracted once, twice or not at all is usually the whole +# question. +# +# A count of zero is ambiguous on its own -- a section with no relocations and +# no section at all both read as zero -- so require the section to exist. A +# test expecting nothing there wants assert_no_section. +assert_reloc_count() +{ + local n + + assert_section "$1" + + n="$(section_relocs "$1" | grep -cE '^[0-9a-f]{8,}')" + [ "$n" = "$2" ] || + fail "expected $2 relocations in '$1', found $n" +} + +# assert_klp_sym <symbol> [object] +# +# A klp symbol is named .klp.sym.<object>.<symbol>,<sympos>. The object +# defaults to any, since most tests care that the reference was converted at +# all rather than which object it resolved against. +assert_klp_sym() +{ + out_symbols | grep -q "\.klp\.sym\.${2:-[^.]*}\.$1," || + fail "expected klp symbol for '$1'" +} + +# assert_klp_sympos <symbol> <sympos> +# +# The number after the comma in .klp.sym.<object>.<symbol>,<sympos> says which +# of several same-named symbols livepatch should resolve to, counting from 1; +# 0 means the name is unique and no disambiguation is needed. Resolving to the +# wrong one is not a load failure, it is a patch quietly wired to the wrong +# object. +assert_klp_sympos() +{ + out_symbols | grep -qE "\.klp\.sym\.[^.]+\.$1,$2([[:space:]]|\$)" || + fail "expected klp symbol for '$1' with sympos $2, found:$( + out_symbols | grep -o "\.klp\.sym\.[^.]*\.$1,[0-9]*" | + sort -u | tr '\n' ' ')" +} + +assert_no_klp_sym() +{ + out_symbols | grep -q "\.klp\.sym\.${2:-[^.]*}\.$1," && + fail "unexpected klp symbol for '$1'" + return 0 +} + +assert_tombstone() +{ + out_symbols | grep -q "\.klp\.tombstone\.$1" || + fail "expected a tombstone for '$1'" +} + +assert_symbol() +{ + out_symbols | grep -qw -- "$1" || + fail "expected symbol '$1' in output" +} + +assert_no_symbol() +{ + out_symbols | grep -qw -- "$1" && + fail "unexpected symbol '$1' in output" + return 0 +} + +# assert_diff_log <regex> +# +# klp diff's combined output, for tests asserting on a diagnostic. Error +# messages are part of the interface when the whole point is that a construct +# gets rejected, and a rejection for the wrong reason is not a pass. +assert_diff_log() +{ + diff_log | grep -qE -- "$1" || + fail "expected '$1' in klp diff output: $(tail -2 "$workdir/diff.log")" +} + +# checksum_of <object> <symbol> +# +# The checksum "klp checksum" recorded for one symbol, as a hex string. +# +# .discard.sym_checksum is an array of { u64 addr; u64 checksum; }, where addr +# is the target of a relocation naming the symbol. Nothing in the section +# itself says which symbol an entry belongs to, so the relocation is what +# locates the entry; the checksum is the eight bytes after it. +# Callers use this in a command substitution, where fail() would only exit the +# subshell and the test would carry on with an empty checksum. So this returns +# non-zero and prints nothing, and the assertions below check for that. +checksum_of() +{ + local obj="$workdir/$1" sym="$2" off + + run_checksum + + off="$($READELF -rW "$obj" 2>/dev/null | + awk -v s="$sym" '/rela\.discard\.sym_checksum/,/^$/ { + if ($5 == s) { print $1; exit } + }')" + + [ -n "$off" ] || return 1 + + $OBJCOPY -O binary --only-section=.discard.sym_checksum \ + "$obj" "$workdir/checksums.bin" 2>/dev/null || return 1 + + dd if="$workdir/checksums.bin" bs=1 skip=$((16#$off + 8)) count=8 \ + status=none | od -An -tx1 | tr -d ' \n' +} + +# assert_checksum_differs <symbol> / assert_checksum_matches <symbol> +# +# Compare what klp checksum recorded for a symbol in the original against the +# patched object. This is what decides whether klp diff treats a function as +# changed, so a test asserting only that the right functions were cloned cannot +# tell a correct checksum from one which happens to differ. +checksum_pair() +{ + orig_checksum="$(checksum_of "$orig_obj" "$1")" + patched_checksum="$(checksum_of "$patched_obj" "$1")" + + [ -n "$orig_checksum" ] || + fail "no checksum recorded for '$1' in $orig_obj" + [ -n "$patched_checksum" ] || + fail "no checksum recorded for '$1' in $patched_obj" +} + +assert_checksum_differs() +{ + checksum_pair "$1" + + [ "$orig_checksum" != "$patched_checksum" ] || + fail "checksum for '$1' unchanged at $orig_checksum, expected it to differ" +} + +assert_checksum_matches() +{ + checksum_pair "$1" + + [ "$orig_checksum" = "$patched_checksum" ] || + fail "checksum for '$1' changed from $orig_checksum to" \ + "$patched_checksum, expected no change" +} + +# run_post_link [expected exit status] +# +# klp post-link runs last in a livepatch build, converting the intermediate +# __klp_relocs.* sections into the .klp.rela.* form the kernel consumes. It +# needs nothing but an object containing those sections, which is what klp diff +# produces, so it runs on out.o here rather than on a built module. Rewrites +# out.o in place, so the out_* helpers show the result afterwards. +run_post_link() +{ + local expect="${1:-0}" rc=0 + + "$OBJTOOL" klp post-link "$workdir/out.o" \ + > "$workdir/post-link.log" 2>&1 || rc=$? + + [ "$rc" = "$expect" ] || + fail "klp post-link exited $rc, expected $expect:" \ + "$(tail -2 "$workdir/post-link.log")" +} + +# The flags readelf prints for a section, or nothing when it has none. The +# leading "[nn]" index is stripped first so the columns can be counted. +section_flags() +{ + out_sections | sed 's/^ *\[[ 0-9]*\] *//' | + awk -v s="$1" '$1 == s && $7 ~ /^[A-Za-z]+$/ { print $7 }' +} + +# assert_section_flag <section> <letter> +# +# SHF_RELA_LIVEPATCH is OS-specific, so readelf renders it as "o". A klp rela +# section which lost it is an ordinary rela section, which the linker may apply +# and the livepatch code will not. +assert_section_flag() +{ + local flags; flags="$(section_flags "$1")" + + [ -n "$flags" ] || + fail "section '$1' has no flags, expected '$2'" + case "$flags" in + *"$2"*) ;; + *) fail "section '$1' has flags '$flags', expected '$2'" ;; + esac +} + +# assert_klp_rela <object> <section> +# +# post-link names the converted sections .klp.rela.<object>.<section>, one per +# base section. Also checks SHF_RELA_LIVEPATCH, since the name alone is not +# what makes the kernel process it. +assert_klp_rela() +{ + local name=".klp.rela.$1.$2" + + out_sections | grep -q "[[:space:]]$(re_quote "$name")[[:space:]]" || + fail "expected section '$name' in output" + + assert_section_flag "$name" o +} + +# assert_livepatch_sym <symbol> +# +# Symbols a klp relocation resolves against live in SHN_LIVEPATCH, which +# readelf prints as "OS [0xff20]" -- llvm-readelf without the space, so match +# either. The kernel resolves these itself at patch load; anything else is a +# symbol the module loader will try, and fail, to resolve normally. +assert_livepatch_sym() +{ + out_symbols | grep -E 'OS ?\[0xff20\]' | grep -qw -- "$1" || + fail "expected '$1' to be an SHN_LIVEPATCH symbol" +} -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* Re: [PATCH 06/58] objtool/klp: Grow the klp test harness vocabulary 2026-09-11 18:42 ` [PATCH 06/58] objtool/klp: Grow the klp test harness vocabulary Song Liu @ 2026-09-11 19:03 ` sashiko-bot 0 siblings, 0 replies; 77+ messages in thread From: sashiko-bot @ 2026-09-11 19:03 UTC (permalink / raw) To: Song Liu, live-patching Cc: puranjay, joe.lawrence, kernel-team, pmladek, peterz, jpoimboe, jikos, mbenes Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Medium] Symbol names containing dots act as unescaped wildcards in grep assertions, contradicting the commit message's goal. - [Medium] Missing regex boundary anchors in section_relocs and assert_tombstone allow partial substring or prefix matches, leading to false positives. -- commit f3b84a0b37e33c6da0d81d19f9f2963fe5db6d2e Author: Song Liu <song@kernel.org> objtool/klp: Grow the klp test harness vocabulary Assertions for what klp diff produces, and for what went into it. The output side covers the things a livepatch is made of: cloned sections and symbols, klp symbols and their sympos, tombstones, relocations by section and by count, the converted .klp.rela sections and their SHF_RELA_LIVEPATCH flag, SHN_LIVEPATCH symbols, recorded checksums, and klp diff's own diagnostics -- a rejection for the wrong reason is not a pass. > diff --git a/tools/objtool/tests/lib.sh b/tools/objtool/tests/lib.sh > index 0a9da178f4dc8..a4d126e6c9cb3 100644 > --- a/tools/objtool/tests/lib.sh > +++ b/tools/objtool/tests/lib.sh [ ... ] > @@ -458,7 +566,251 @@ assert_patched() > > assert_not_patched() > { > - out_sections | grep -q "[[:space:]].text.$1[[:space:]]" && > + out_sections | grep -q "[[:space:]]$(re_quote ".text.$1")[[:space:]]" && > fail "function '$1' should not have been cloned" > return 0 > } > + > +# section_relocs <section> > +# > +# The relocations against one section. readelf prints every relocation section > +# in turn, so a test asking about ".smp_locks" has to cut its block out of the > +# listing first. > +section_relocs() > +{ > + local sec="${1//./\\.}" > + > + out_relocs | awk "/rela$sec/,/^\$/" [Severity: Medium] Will this awk range pattern match more sections than intended? Without a trailing anchor like '/rela$sec$/', searching for a section like ".text" might match and extract relocation blocks for both ".rela.text" and ".rela.text.unlikely". This could cause assertions like assert_reloc_count() to inadvertently count entries across all matched blocks. > +} > + > +assert_reloc_sym() > +{ > + section_relocs "$1" | grep -qw -- "$2" || [Severity: Medium] Does this grep command need to use re_quote() for the symbol name ($2)? Since compilers can generate symbol names with dots (e.g., foo.isra.0), leaving the dot unescaped could allow grep to treat it as a wildcard character, matching things like "foo_isra_0". The commit message mentions names are quoted before reaching grep to avoid this, but it appears some symbol assertions might be missing this quoting. > + fail "expected a relocation to '$2' in '$1'" > +} [ ... ] > +assert_klp_sym() > +{ > + out_symbols | grep -q "\.klp\.sym\.${2:-[^.]*}\.$1," || [Severity: Medium] Should the symbol name ($1) be escaped here as well? An unescaped dot in the symbol name could act as a wildcard in this regular expression, allowing false positive matches in the test harness. > + fail "expected klp symbol for '$1'" > +} > + > +# assert_klp_sympos <symbol> <sympos> > +# > +# The number after the comma in .klp.sym.<object>.<symbol>,<sympos> says which > +# of several same-named symbols livepatch should resolve to, counting from 1; > +# 0 means the name is unique and no disambiguation is needed. Resolving to the > +# wrong one is not a load failure, it is a patch quietly wired to the wrong > +# object. > +assert_klp_sympos() > +{ > + out_symbols | grep -qE "\.klp\.sym\.[^.]+\.$1,$2([[:space:]]|\$)" || [Severity: Medium] Is it possible for an unescaped dot in $1 to cause false matches in this extended regular expression? > + fail "expected klp symbol for '$1' with sympos $2, found:$( > + out_symbols | grep -o "\.klp\.sym\.[^.]*\.$1,[0-9]*" | > + sort -u | tr '\n' ' ')" > +} [ ... ] > +assert_tombstone() > +{ > + out_symbols | grep -q "\.klp\.tombstone\.$1" || [Severity: Medium] Could this pattern match partial symbol names? Without a trailing word boundary or end-of-line anchor, querying for a tombstone for "foo" might falsely succeed if a tombstone for "foo_bar" exists. Additionally, does $1 need to be escaped here so that dots in the symbol name are treated literally? > + fail "expected a tombstone for '$1'" > +} > + > +assert_symbol() > +{ > + out_symbols | grep -qw -- "$1" || [Severity: Medium] Does this direct call to grep need to escape the symbol name? Like the other assertions, if a symbol name contains a dot, it could act as an unescaped wildcard character here. > + fail "expected symbol '$1' in output" > +} -- Sashiko AI review · https://sashiko.dev/#/patchset/20260911185031.1534046-1-song@kernel.org?part=6 ^ permalink raw reply [flat|nested] 77+ messages in thread
* [PATCH 07/58] objtool/klp: Give each run one working directory, one per test inside it 2026-09-11 18:42 [PATCH 00/58] Unit test framework for klp-build toolchain Song Liu ` (5 preceding siblings ...) 2026-09-11 18:42 ` [PATCH 06/58] objtool/klp: Grow the klp test harness vocabulary Song Liu @ 2026-09-11 18:42 ` Song Liu 2026-09-11 18:42 ` [PATCH 08/58] objtool/klp: Run the klp tests under set -u Song Liu ` (19 subsequent siblings) 26 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:42 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu Each test made its own mktemp directory, so a run scattered them through /tmp among everything else using the same names, and there was no way to inspect what a failing test built -- the objects are gone the moment it exits, which is right every time but the one that matters. Give the run one directory and each test a subdirectory of it, named for the test and nested the way the source is: /tmp/klp-tests.3zC6oyfd/ generic/test-basic/{orig.o,patched.o,out.o,Module.symvers,...} x86/test-kcfi/... --keep then reports one path instead of forty, and removing it is one command. A test run by hand still falls back to a temp directory of its own, and still says where that went. Detecting a leak comes free. Cleanup is now "remove each test's directory, then rmdir the run's", and the rmdir fails if anything is left -- so a test which dies without running its own cleanup is reported rather than quietly leaving something behind. preflight names the directory the tests build under, since mktemp honours TMPDIR and a run in a container is otherwise silent about where its work went. The runner says what to do only when it is relevant: on a failure without --keep, that the option exists; with it, how many directories are waiting. Usage text is not read while something is broken. Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- tools/objtool/tests/lib.sh | 23 ++++++++++++++++++----- tools/objtool/tests/run-tests.sh | 26 ++++++++++++++++++++++++-- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/tools/objtool/tests/lib.sh b/tools/objtool/tests/lib.sh index a4d126e6c9cb..08788510e722 100644 --- a/tools/objtool/tests/lib.sh +++ b/tools/objtool/tests/lib.sh @@ -120,6 +120,7 @@ klp_preflight() # objtool $OBJTOOL (klp: yes) # compiler $cc_version # arch $KLP_TEST_ARCH$([ "$arch" = "$host" ] || echo " (host $host, cross)") +# tmpdir ${TMPDIR:-/tmp} (each test builds in a fresh directory here) EOF } @@ -179,15 +180,27 @@ xpass() exit 1 } -cleanup() { [ -n "$workdir" ] && rm -rf "$workdir"; } +cleanup() +{ + [ -n "$workdir" ] || return 0 + + if [ -n "${KLP_TEST_KEEP:-}" ]; then + [ -n "${KLP_TEST_WORKDIR:-}" ] || echo "# kept $workdir" + return 0 + fi + + rm -rf "$workdir" +} # setup [exported symbol...] setup() { - # The environment was checked once when this file was sourced, so there - # is nothing to verify here: objtool exists at the resolved path, has - # klp support, and $CC works. - workdir="$(mktemp -d)" || fail "mktemp failed" + if [ -n "${KLP_TEST_WORKDIR:-}" ]; then + workdir="$KLP_TEST_WORKDIR" + mkdir -p "$workdir" || fail "cannot create $workdir" + else + workdir="$(mktemp -d)" || fail "mktemp failed" + fi trap cleanup EXIT export_syms "$@" diff --git a/tools/objtool/tests/run-tests.sh b/tools/objtool/tests/run-tests.sh index 6b937fc7f5bd..e6f1ac1b5d0d 100755 --- a/tools/objtool/tests/run-tests.sh +++ b/tools/objtool/tests/run-tests.sh @@ -23,13 +23,17 @@ export LC_ALL=C usage() { cat <<EOF -usage: $(basename "$0") [test...] +usage: $(basename "$0") [-k|--keep] [test...] Run the objtool klp tests for this architecture: everything in generic/, plus everything in the directory named for it. With no arguments, runs all of them. A test may be named with or without its "test-" prefix and ".sh" suffix, and is looked for in both directories. +Options: + -k, --keep do not delete each test's working directory; print its path, + so the objects a failing test built can be looked at + Environment: OBJTOOL objtool binary to test (default ../objtool) CC compiler used to build fixtures (default gcc) @@ -45,6 +49,7 @@ cd "$(dirname "$0")" || exit 1 while [ $# -gt 0 ]; do case "$1" in -h|--help) usage ;; + -k|--keep) export KLP_TEST_KEEP=1; shift ;; --) shift; break ;; -*) echo "unknown option: $1" >&2; usage 1 ;; *) break ;; @@ -98,12 +103,18 @@ else done fi +# One directory for the whole run, one per test inside it, mirroring the +# source layout. A run then leaves a single thing behind instead of 39 +# scattered among everything else using mktemp. +rundir="$(mktemp -d "${TMPDIR:-/tmp}/klp-tests.XXXXXXXX")" || + { echo "Bail out! cannot create a working directory" >&2; exit 1; } + echo "1..${#tests[@]}" pass=0 fail=0 static_skip=0 probe_skip=0 xfail=0 xpass=0 for t in "${tests[@]}"; do - out="$(./"$t" 2>&1)" + out="$(KLP_TEST_WORKDIR="$rundir/${t%.sh}" ./"$t" 2>&1)" rc=$? # A test prints one result line, but it is not necessarily the only @@ -156,4 +167,15 @@ done echo "# pass:$pass fail:$fail static-skip:$static_skip" \ "probe-skip:$probe_skip xfail:$xfail xpass:$xpass" +# A failure is the one time the objects matter, and by default they are +# already gone. Say so then rather than in the usage text nobody reads while +# something is broken. +if [ -n "${KLP_TEST_KEEP:-}" ]; then + echo "# working directories kept in $rundir -- inspect, then rm -rf it" +elif ! rmdir "$rundir"/*/ "$rundir" 2>/dev/null; then + echo "# $rundir was not empty; a test did not clean up after itself" +elif [ "$fail" != 0 ] || [ "$xpass" != 0 ]; then + echo "# re-run with --keep to hold on to what a failing test built" +fi + [ "$fail" = 0 ] && [ "$xpass" = 0 ] -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 08/58] objtool/klp: Run the klp tests under set -u 2026-09-11 18:42 [PATCH 00/58] Unit test framework for klp-build toolchain Song Liu ` (6 preceding siblings ...) 2026-09-11 18:42 ` [PATCH 07/58] objtool/klp: Give each run one working directory, one per test inside it Song Liu @ 2026-09-11 18:42 ` Song Liu 2026-09-11 18:42 ` [PATCH 09/58] objtool/klp: Document the klp test harness Song Liu ` (18 subsequent siblings) 26 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:42 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu Most of these assertions end in a grep, and an empty pattern matches anything. So a mistyped variable name in a test does not fail: it expands to nothing, the grep succeeds, and the test reports a pass having checked nothing. assert_section "$sectoin_nmae" becomes a search for two adjacent spaces, which readelf's column padding satisfies on every line. Refuse unset variables. lib.sh guards the few genuinely optional expansions it has with ${x:-}. Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- tools/objtool/tests/lib.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/objtool/tests/lib.sh b/tools/objtool/tests/lib.sh index 08788510e722..4d8401ee8b41 100644 --- a/tools/objtool/tests/lib.sh +++ b/tools/objtool/tests/lib.sh @@ -8,6 +8,8 @@ # codegen varies between compilers and golden files would report churn instead # of regressions. +set -u + TESTS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # Tests live in generic/ or in an architecture directory beside it, and each -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 09/58] objtool/klp: Document the klp test harness 2026-09-11 18:42 [PATCH 00/58] Unit test framework for klp-build toolchain Song Liu ` (7 preceding siblings ...) 2026-09-11 18:42 ` [PATCH 08/58] objtool/klp: Run the klp tests under set -u Song Liu @ 2026-09-11 18:42 ` Song Liu 2026-09-11 19:00 ` sashiko-bot 2026-09-11 18:42 ` [PATCH 10/58] objtool: Keep failing test workdirs by default Song Liu ` (17 subsequent siblings) 26 siblings, 1 reply; 77+ messages in thread From: Song Liu @ 2026-09-11 18:42 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu Two documents, for two different readers. klp-test-design.rst is for someone deciding whether to trust the suite or change it: what makes unit tests possible for a pipeline that normally needs two kernel builds (objtool has no configuration-dependent logic, so a test reproduces a configuration by reproducing its input), how tests are selected per architecture, and what the outcome classification is for. klp-write-tests.rst is the procedure for adding one, and is written to be handed to someone -- or something -- with the instruction "follow this and write a test for commit <sha>", or "port the case at <location> from another harness". It leads with the rule that matters: a test is not finished until you have watched it fail with the code broken. Then the traps, each of which this suite has already fallen into -- a fixture the compiler optimised away, a named char[] which never reaches the string path because that keys on SHF_STRINGS, per-function sections hiding the movement a test was about. Some of this was previously written as comments in lib.sh. That was the wrong place: guidance on how to write a test is read once by an author, while every comment in the harness is read by everyone who opens it. Explanations of why the code is as it is stay where they were. Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../objtool/Documentation/klp-test-design.txt | 272 ++++++++++++++++++ .../objtool/Documentation/klp-write-tests.txt | 260 +++++++++++++++++ 2 files changed, 532 insertions(+) create mode 100644 tools/objtool/Documentation/klp-test-design.txt create mode 100644 tools/objtool/Documentation/klp-write-tests.txt diff --git a/tools/objtool/Documentation/klp-test-design.txt b/tools/objtool/Documentation/klp-test-design.txt new file mode 100644 index 000000000000..ec56bfbc2c93 --- /dev/null +++ b/tools/objtool/Documentation/klp-test-design.txt @@ -0,0 +1,272 @@ +.. SPDX-License-Identifier: GPL-2.0 + +====================================== +Design of the objtool klp test harness +====================================== + +tools/objtool/tests/ holds unit tests for the klp subcommands of +objtool -- ``klp checksum``, ``klp diff``, ``klp post-link`` and +``--klp-symids`` -- which together turn two builds of the kernel into a +livepatch module. + +This document explains how the harness is built and why. For the rules to +follow when adding a test, see klp-write-tests.txt. + + +TL;DR +===== + +One run covers one compiler and one architecture; CI runs the combinations. +Build objtool first -- it needs libelf and libxxhash -- and the same ARCH is +used for both steps. + +Natively, with gcc:: + + make -C tools/objtool + make -C tools/objtool tests + +Natively, with clang -- LLVM=1 additionally selects the LLVM binutils:: + + CC=clang make -C tools/objtool tests + LLVM=1 make -C tools/objtool tests + +Cross, with gcc -- an arm64 host running the x86 tests:: + + ARCH=x86_64 CROSS_COMPILE=x86_64-linux-gnu- make -C tools/objtool + ARCH=x86_64 CROSS_COMPILE=x86_64-linux-gnu- make -C tools/objtool tests + +Cross, with clang. It defaults to the host triple however it is invoked, so +--target= is what makes it emit x86; OBJCOPY is needed because BFD's is +usually built for the host's target alone:: + + ARCH=x86_64 make -C tools/objtool + ARCH=x86_64 CC="clang --target=x86_64-linux-gnu" OBJCOPY=llvm-objcopy \ + make -C tools/objtool tests + +A run ends with a totals line; anything other than fail:0 is a real result:: + + # pass:48 fail:0 static-skip:1 probe-skip:0 xfail:0 xpass:0 + +Useful extras:: + + tools/objtool/tests/run-tests.sh basic # one test, by name + tools/objtool/tests/run-tests.sh --keep basic # and keep what it built + + +Why unit tests are possible at all +================================== + +klp-build is a pipeline: build the kernel twice, checksum both, diff them, +link the result. Testing that end to end means two kernel builds per case, +which is too slow to run often and too heavy to keep in the tree. + +Three properties make a much cheaper test possible. + +**objtool has no configuration-dependent logic.** It never reads ``.config``. +Every ``CONFIG_`` string in its source is a comment or one error message, and +its only build-time conditionals are driven by host libraries and the target +architecture. Configuration reaches objtool through exactly two channels: the +``objtool-args-$(CONFIG_*)`` lines in scripts/Makefile.lib, and the +contents of the object handed to it. + +**The klp subcommands use none of the first channel.** Of objtool's options +they consult three -- ``checksum``, ``debug_checksum``, ``dryrun`` -- all from +their own command line. So klp behaviour varies with configuration *only* +through the input object. + +**Therefore a test can reproduce any configuration's behaviour by reproducing +its input.** Compile a small freestanding fixture with the flags that +configuration would have used, and objtool cannot tell the difference. No +kernel, no ``.config``, no object cache. + +The whole suite runs in a few seconds. + + +Shape of a test +=============== + +Each test compiles one fixture twice -- once plain, once with ``-DPATCHED`` -- +runs ``klp checksum`` over both, diffs them, and asserts on properties of the +output object:: + + . "$(dirname "$0")/../lib.sh" + + setup + build_pair basic.c + + assert_input_symbol changed + run_diff + + assert_patched changed + assert_not_patched untouched + + pass "changed function cloned, unchanged function left alone" + +Assertions check properties, never recorded output. Codegen varies between +compilers and versions, so a golden file would report churn rather than +regressions. + + +Layout +====== + +:: + + tools/objtool/tests/ + lib.sh the harness: everything a test may call + run-tests.sh selects, runs and classifies + generic/ + test-*.sh + fixtures/*.c + x86/ + test-*.sh + fixtures/*.c + +Which architecture a test is for is expressed by where it lives. The runner +executes ``generic/`` plus the directory matching this architecture, so a test +which cannot apply is not run rather than running in order to report that it +did not. There is no ``x86_only`` helper, and no lookup letting an +architecture fixture shadow a generic one: an architecture-specific test +carries its own fixtures. + +Compilers cannot be expressed the same way, because CI varies ``CC`` over the +same tree. A compiler requirement stays a declaration inside the test +(``gcc_only``, ``clang_only``). + + +The environment is established once +=================================== + +Sourcing lib.sh runs ``klp_preflight``, which checks that objtool +exists and has klp support, that ``$CC`` works, that the binutils are present, +and which architecture this is. The answers are exported, so: + +* ``run-tests.sh`` sources lib.sh too, and therefore knows the + architecture before it chooses which tests to run; +* each test inherits the answers rather than repeating the work; +* a test run on its own establishes them for itself. + +Preflight answers only whether the suite can run at all. A suite which cannot +run must not exit 0 looking like one which passed, so a missing objtool fails +the whole run with a TAP ``Bail out!`` rather than skipping each test in turn. +What a *particular* compiler can do is a different question, left to the test +which cares. + + +Outcomes +======== + +Output is TAP. The distinction the harness cares most about is between kinds +of skip, because a skip is how a suite quietly stops testing anything: + +``declared`` + The test said in advance it does not apply -- ``gcc_only`` on a clang run. + Expected indefinitely. + +``probe`` + The construct did not turn up in the built object this time. Weaker: one + which becomes permanent is a fixture that has stopped testing anything. + +``undeclared`` + Counted as a **failure**. A test which gives up for a reason it never + declared is a hole, not an outcome. + +``xfail``/``xpass`` come with them, so a known failure is reported rather than +commented out, and one which starts passing says so instead of going quietly +green. + +The runner classifies the TAP result line, not everything a test printed: +objtool warns on stderr and that output is captured, so a stray line ahead of +the result would otherwise leave the exit status to decide -- and an expected +failure exits 0. + +A run ends with a totals line:: + + # pass:48 fail:0 static-skip:1 probe-skip:0 xfail:0 xpass:0 + +and reports what it left out:: + + # not run: 5 tests in x86/ (this run is arm64) + + +Working directories +=================== + +A run gets one directory; each test gets a subdirectory of it, mirroring the +source layout:: + + /tmp/klp-tests.XXXXXXXX/ + generic/test-basic/{orig.o,patched.o,out.o,Module.symvers,...} + x86/test-kcfi/... + +``--keep`` leaves it and reports the one path. Otherwise each test removes its +own directory and the runner ``rmdir``s the run's -- which fails if anything +was left behind, so a test which dies without cleaning up is reported rather +than silently leaking. + + +Running +======= + +:: + + make -C tools/objtool # needs libelf and libxxhash + make -C tools/objtool tests + + CC=clang make -C tools/objtool tests # the other toolchain + LLVM=1 make -C tools/objtool tests # and its binutils too + + tools/objtool/tests/run-tests.sh --keep basic # one test, keep its objects + +A run covers one compiler and one architecture; CI runs the combinations. + +Cross-compiled runs +------------------- + +objtool klp is built only where ARCH_HAS_KLP is set, which today means x86 -- +so an arm64 machine cannot run any of this natively. It can run all of it +cross, because objtool is a host tool that only reads and rewrites ELF, and +the tests only compile fixtures and inspect the objects. Nothing has to +execute target code. + +:: + + ARCH=x86_64 CROSS_COMPILE=x86_64-linux-gnu- make -C tools/objtool + ARCH=x86_64 CROSS_COMPILE=x86_64-linux-gnu- make -C tools/objtool tests + +objtool itself stays a native binary: it is built with HOSTCC, not CC, so +setting a cross compiler cannot produce one the host is unable to run. ARCH +selects both the objtool target and the directory of tests to run. + +clang needs telling, since it defaults to the host triple however it is +invoked. The fixtures include no kernel headers, so no sysroot is needed: + +:: + + ARCH=x86_64 CC="clang --target=x86_64-linux-gnu" \ + OBJCOPY=llvm-objcopy make -C tools/objtool tests + +CROSS_COMPILE picks the binutils, and each can be overridden on its own. +readelf reads any target and rarely needs overriding; BFD's objcopy is usually +built for the host's alone, hence OBJCOPY=llvm-objcopy above, or install +binutils-multiarch. + +Either readelf will do. The assertions read readelf's output, and the two +spell some of it differently -- GNU prints "OS [0xff20]" for SHN_LIVEPATCH +where llvm-readelf prints "OS[0xff20]" -- so they accept both. + +Getting this wrong is easy and the harness refuses rather than producing a +misleading result. "CC=clang ARCH=x86_64" alone selects the x86 tests and +then builds arm64 objects; preflight compiles a probe object, hands it to +objtool, and stops the run if they disagree about the architecture, or if +ARCH does not match what the compiler emits. + + +What this does not cover +======================== + +These are unit tests for objtool's klp subcommands. They do not build a +kernel, do not run scripts/livepatch/klp-build, and do not load a +livepatch. Behaviour which only appears when the kernel applies a patch -- +the module loader refusing a relocation, late module patching ordering -- has +to be tested by booting, and is out of scope here. diff --git a/tools/objtool/Documentation/klp-write-tests.txt b/tools/objtool/Documentation/klp-write-tests.txt new file mode 100644 index 000000000000..e85253eaaa44 --- /dev/null +++ b/tools/objtool/Documentation/klp-write-tests.txt @@ -0,0 +1,260 @@ +.. SPDX-License-Identifier: GPL-2.0 + +===================================== +Writing a test for objtool's klp code +===================================== + +Instructions for adding a test to tools/objtool/tests/. Read +klp-test-design.txt first if you need to know how the harness works; this +document is the procedure and the rules. + +Two kinds of request bring you here: + +* *"write a test for commit <sha>"* -- a fix went in without one. +* *"port the test at <location>, written against another harness"* -- a case + exists elsewhere and should live in tree. + +Both follow the same procedure. + + +The one rule that matters +========================= + +**A test is not finished until you have watched it fail.** + +Break the thing it guards -- revert the fix, or sabotage the exact line -- and +confirm the test fails. Then restore and confirm it passes. A test that has +never failed is not known to test anything, and this suite has produced +several that passed against deliberately broken code: + +* an alternatives fixture whose empty entry pointed at its own end label rather + than the neighbour's replacement, so the bug it guarded made no difference; +* a sympos fixture where symbol-table order and address order agreed, so + counting and reading the linked image gave the same answer; +* a string fixture using a named ``char[]``, which never reached the + contents-hashing path because that keys on ``SHF_STRINGS``; +* a static array the compiler proved constant, folded to zero, and emitted no + relocation for -- so the two builds were byte-identical. + +Every one looked correct. Say in the commit message how you verified, and if +you could not isolate the behaviour to a single line, **say that too** rather +than implying otherwise. + + +Procedure +========= + +1. **Read the fix.** What input reaches the broken line? What is observable + in the output object when it misbehaves -- a missing section, a relocation + naming the wrong symbol, an unchanged checksum, a rejected build? If + nothing is observable, stop and say so; see `When to give up`_. + +2. **Decide where it lives.** ``generic/`` unless the fixture needs + architecture-specific assembly or the behaviour is architecture-specific, + in which case ``x86/`` (or a new directory named for the architecture). + +3. **Write the fixture** in the same directory's ``fixtures/``. Reuse an + existing one if it already produces the shape; add a ``-D`` knob rather + than copying a fixture to change one line. + +4. **Write the test.** Assert the *premise* before the result -- see + `State the premise`_. + +5. **Verify by breaking the code.** Then restore. + +6. **Run the whole suite under both compilers**:: + + make -C tools/objtool tests + CC=clang make -C tools/objtool tests + +7. **Commit** the test and its fixture together, alone. One test per commit. + + +Writing the fixture +=================== + +Fixtures are freestanding C. No kernel headers -- write out the kernel +structure by hand if you need one, as the existing special-section fixtures do. + +Every fixture needs a ``.modinfo`` name, because klp diff reads the object's +module name from it:: + + static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +Use ``MODNAME`` if the test needs to vary it. Note that most fixtures hardcode +``vmlinux``: passing ``-DMODNAME`` to one that does silently does nothing and +the test quietly becomes a vmlinux test. + +The patched build is selected with ``-DPATCHED``. For a fixture with several +variants, gate each on both, so the original is always the baseline:: + + #if defined(PATCHED) && defined(WHICH_CALL) + r = callee_b(x); + #else + r = callee_a(x); + #endif + +and select one per build: ``build_pair foo.c -DWHICH_CALL``. Without the +``defined(PATCHED)`` the flag applies to *both* builds and nothing differs. + +Traps that have bitten before +----------------------------- + +* **The compiler optimises your fixture away.** A static never written is + proved constant, its reads folded, and no relocation emitted. Add a writer + the compiler cannot see through. +* **String literals versus named arrays.** The contents-hashing path keys on + ``SHF_STRINGS``, which the compiler sets on the mergeable section a *literal* + lands in, not on a ``char[]`` given a section of its own. +* **Per-function sections hide movement.** With the default + ``-ffunction-sections`` every function sits at offset 0 of its own section, + so nothing ever moves. A test about position needs + ``build_pair foo.c -fno-function-sections``. +* **Special sections need boundaries.** Either an entsize on the section or an + ``ANNOTATE_DATA_SPECIAL`` annotation, or klp diff reports "missing special + section entsize or annotations". Their targets need real (global) symbols, + or it reports "failed to convert reloc sym". +* **Do not hand-write what objtool generates.** ``.static_call_sites``, + ``.mcount_loc``, ``.ibt_endbr_seal`` and ORC come from objtool's check pass. + Call ``run_objtool_check --mcount`` and let it build them; a hand-written + copy tests your reading of the format, not the format. + + +Writing the test +================ + +Start from the shortest existing test, generic/test-basic.sh. + +State the premise +----------------- + +A test which asserts only on the output passes when the compiler never emitted +the construct in the first place, and reads as coverage it does not have. Say +what the input must contain:: + + assert_input_section __jump_table # the fixture must produce it -> fail + require_input_section .kcfi_traps # this compiler may not -> skip + +Prefer ``assert_*``. Reach for ``require_*`` only where absence genuinely +depends on compiler version or flags, and follow it with something +unconditional so the test can never be entirely vacuous. + +Where a test would otherwise duplicate a sibling, assert what makes it +different. ``test-jump-label-module-static-key`` checks that the key really is +reached through its section symbol -- without that it is a second copy of +``test-jump-label-module-key``. + +Assert both directions +---------------------- + +Check that the right thing happened *and* that the wrong thing did not. A klp +diff which clones everything is as wrong as one which clones nothing:: + + assert_patched changed + assert_not_patched untouched + +Skips +----- + +* ``gcc_only``/``clang_only`` -- a settled fact about the compiler. Declared, + so it reads as expected forever. +* ``probe_skip`` -- this toolchain did not produce the construct. Include what + to do about it if there is anything:: + + probe_skip "no matching clang/lld pair for a ThinLTO link; set THIN_CC and THIN_LD to one" + +* A bare ``skip`` is **counted as a failure**. Never use it. + +Standing in for a kernel configuration +-------------------------------------- + +``FIXTURE_CFLAGS`` is what a fixture is built with. Since objtool reads no +``.config``, changing these flags is how a test covers a configuration without +building a kernel. Two ways: + +* trailing arguments to ``build_pair``/``build_one``, which win, and cover + anything expressible as a negation:: + + build_pair foo.c -fno-function-sections + +* otherwise assign ``FIXTURE_CFLAGS`` before building. + +Either way **say in a comment which kernel configuration the change stands in +for**. A flag with no stated motive is indistinguishable from a mistake. + + +What the test's comment must say +================================ + +The comment at the top is the test's justification. It should let a reader +decide, without archaeology, whether a skip or a failure matters. Include: + +* **what breaks** in the running kernel if the behaviour regresses -- not the + mechanism, the consequence; +* **why it is not caught otherwise**, which is usually "nothing fails at build + time"; +* **the fix commit** it guards, if there is one; +* **anything load-bearing about the fixture** that is not obvious, especially + anything you got wrong first. + +That last point is the one people skip. If the fixture has to be built without +per-function sections, or the static must not be named ``__warned``, or the key +must be file-local -- write it down, or the next person will simplify it away. + + +Porting a test from another harness +=================================== + +Read the original's *case*, not its code. The other harness probably builds a +real kernel module; here you write freestanding C. A transliteration will +usually test something else. + +* Work out which objtool behaviour the case exercises, then produce that shape + the cheapest way here. +* Verify by breaking the code, exactly as for a new test -- a port is not + correct because the original was. +* If the original names a fix commit, cite it. +* Credit the source in the commit message with the trailers the original + carried, followed by your own. + +Sometimes the port shows the case is already covered, and sometimes it shows +the case cannot be reproduced here. Both are results; report them rather than +committing something that passes vacuously. + + +When to give up +=============== + +Some behaviour cannot be reached from a compiled fixture. Say so, with what +you tried, instead of committing a test that passes either way. Examples that +were genuinely abandoned: + +* a memory leak -- needs valgrind, not an assertion on ELF; +* ``mkstemp`` with long paths, and other I/O edge cases; +* a NULL dereference reachable only through a debug path; +* changes made redundant by a fallback: removing the code changes no output + because something else already handles the case; +* a fix whose code has since been rewritten, so there is nothing left to + revert. + +Also stop when the behaviour depends on something outside the fixture's +control -- an ELF library's handling of empty sections, or a compiler version's +naming of anonymous data. A test which passes for you and skips for everyone +else is worse than none. + + +Checklist +========= + +Before committing: + +* the test fails with the code broken, and passes with it fixed +* the whole suite passes under **both** gcc and clang +* the premise is asserted, not assumed +* both directions are asserted where that applies +* no bare ``skip`` +* the fixture is in the same directory as the test +* the comment names the consequence, the fix commit, and anything load-bearing +* the commit contains one test and its fixtures, and nothing else +* the commit message says how you verified it -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* Re: [PATCH 09/58] objtool/klp: Document the klp test harness 2026-09-11 18:42 ` [PATCH 09/58] objtool/klp: Document the klp test harness Song Liu @ 2026-09-11 19:00 ` sashiko-bot 0 siblings, 0 replies; 77+ messages in thread From: sashiko-bot @ 2026-09-11 19:00 UTC (permalink / raw) To: Song Liu, live-patching Cc: pmladek, joe.lawrence, kernel-team, jpoimboe, mbenes, peterz, jikos, puranjay Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Medium] The commit message references the newly added documentation files with the '.rst' extension, but the actual files are created with the '.txt' extension. - [Low] The 'Assisted-by' trailer is malformed and missing an email address. -- commit 7b950355f8a7387d83ee352b14b64773b25d4df6 Author: Song Liu <song@kernel.org> objtool/klp: Document the klp test harness This patch adds documentation for the klp test harness, including design details and guidelines for writing tests. > klp-test-design.rst is for someone deciding whether to trust the suite or > change it: what makes unit tests possible for a pipeline that normally > needs two kernel builds (objtool has no configuration-dependent logic, so a > test reproduces a configuration by reproducing its input), how tests are > selected per architecture, and what the outcome classification is for. > > klp-write-tests.rst is the procedure for adding one, and is written to be > handed to someone -- or something -- with the instruction "follow this and [ ... ] > Assisted-by: Claude:claude-opus-5 [Severity: Low] Does this trailer need an email address? The "Assisted-by" trailer appears to be malformed without one. > diff --git a/tools/objtool/Documentation/klp-test-design.txt b/tools/objtool/Documentation/klp-test-design.txt > new file mode 100644 > index 0000000000000..ec56bfbc2c930 > --- /dev/null > +++ b/tools/objtool/Documentation/klp-test-design.txt [Severity: Medium] The commit message references the newly added documentation files with the ".rst" extension, but the actual files created by the patch use the ".txt" extension. Should the commit message be updated to match the files, or should the files be renamed to .rst? -- Sashiko AI review · https://sashiko.dev/#/patchset/20260911185031.1534046-1-song@kernel.org?part=9 ^ permalink raw reply [flat|nested] 77+ messages in thread
* [PATCH 10/58] objtool: Keep failing test workdirs by default 2026-09-11 18:42 [PATCH 00/58] Unit test framework for klp-build toolchain Song Liu ` (8 preceding siblings ...) 2026-09-11 18:42 ` [PATCH 09/58] objtool/klp: Document the klp test harness Song Liu @ 2026-09-11 18:42 ` Song Liu 2026-09-11 19:07 ` sashiko-bot 2026-09-11 18:42 ` [PATCH 11/58] objtool: Forward toolchain variables to the klp test runner Song Liu ` (16 subsequent siblings) 26 siblings, 1 reply; 77+ messages in thread From: Song Liu @ 2026-09-11 18:42 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu From: Joe Lawrence <joe.lawrence@redhat.com> The klp test runner deletes each test's working directory when the test finishes. On failure that removes diff.log, out.o and the fixture objects before the developer can look at them, and the only hint was to re-run (something) with --keep: $ make -C tools/objtool tests ... not ok - test-special-section: expected section '.kcfi_traps' in output ... # pass:49 fail:1 static-skip:1 probe-skip:0 xfail:0 xpass:0 # re-run with --keep to hold on to what a failing test built Change the default behavior to save failing test working directories. This is friendlier to the drive-by tester. Provide the power-user with a KEEP Makefile variable that can optionally retain 'all' or 'none' of those directories. run-tests.sh --keep remains an alias for KEEP=all. Signed-off-by: Joe Lawrence <joe.lawrence@redhat.com> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../objtool/Documentation/klp-test-design.txt | 14 ++-- tools/objtool/Makefile | 2 +- tools/objtool/tests/lib.sh | 17 +++-- tools/objtool/tests/run-tests.sh | 69 ++++++++++++++----- 4 files changed, 72 insertions(+), 30 deletions(-) diff --git a/tools/objtool/Documentation/klp-test-design.txt b/tools/objtool/Documentation/klp-test-design.txt index ec56bfbc2c93..67d20bf0dc10 100644 --- a/tools/objtool/Documentation/klp-test-design.txt +++ b/tools/objtool/Documentation/klp-test-design.txt @@ -199,10 +199,11 @@ source layout:: generic/test-basic/{orig.o,patched.o,out.o,Module.symvers,...} x86/test-kcfi/... -``--keep`` leaves it and reports the one path. Otherwise each test removes its -own directory and the runner ``rmdir``s the run's -- which fails if anything -was left behind, so a test which dies without cleaning up is reported rather -than silently leaking. +By default (``KEEP=failed``) only failing tests keep their directories; the +runner reports where they are. ``KEEP=all`` keeps every test's directory; +``KEEP=none`` removes them all. The runner ``rmdir``s the run directory when +it is empty -- which fails if anything was left behind unexpectedly, so a test +which dies without cleaning up is reported rather than silently leaking. Running @@ -216,7 +217,10 @@ Running CC=clang make -C tools/objtool tests # the other toolchain LLVM=1 make -C tools/objtool tests # and its binutils too - tools/objtool/tests/run-tests.sh --keep basic # one test, keep its objects + make -C tools/objtool tests KEEP=all # keep every test's workdir + make -C tools/objtool tests KEEP=none # remove all workdirs + + tools/objtool/tests/run-tests.sh basic # one test; failures kept by default A run covers one compiler and one architecture; CI runs the combinations. diff --git a/tools/objtool/Makefile b/tools/objtool/Makefile index 2c200d05c276..ed5cf146ef2e 100644 --- a/tools/objtool/Makefile +++ b/tools/objtool/Makefile @@ -153,7 +153,7 @@ mrproper: clean tests: $(OBJTOOL) $(Q)OBJTOOL=$(abspath $(OBJTOOL)) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) \ - $(srctree)/tools/objtool/tests/run-tests.sh + KEEP=$(KEEP) $(srctree)/tools/objtool/tests/run-tests.sh FORCE: diff --git a/tools/objtool/tests/lib.sh b/tools/objtool/tests/lib.sh index 4d8401ee8b41..6c607a043502 100644 --- a/tools/objtool/tests/lib.sh +++ b/tools/objtool/tests/lib.sh @@ -149,7 +149,7 @@ orig_obj=orig.o patched_obj=patched.o pass() { echo "ok - $test_name${1:+: $1}"; exit 0; } -fail() { echo "not ok - $test_name: $1"; exit 1; } +fail() { KLP_TEST_FAILED=1; echo "not ok - $test_name: $*"; exit 1; } # Two kinds of skip, and the runner tells them apart. # @@ -178,6 +178,7 @@ xfail() xpass() { + KLP_TEST_FAILED=1 echo "ok - $test_name${1:+: $1} # TODO expected failure, but passed" exit 1 } @@ -186,12 +187,14 @@ cleanup() { [ -n "$workdir" ] || return 0 - if [ -n "${KLP_TEST_KEEP:-}" ]; then - [ -n "${KLP_TEST_WORKDIR:-}" ] || echo "# kept $workdir" - return 0 - fi - - rm -rf "$workdir" + case "${KLP_TEST_KEEP:-failed}" in + all) return 0 ;; + none) rm -rf "$workdir" ;; + failed|*) + [ -n "${KLP_TEST_FAILED:-}" ] && return 0 + rm -rf "$workdir" + ;; + esac } # setup [exported symbol...] diff --git a/tools/objtool/tests/run-tests.sh b/tools/objtool/tests/run-tests.sh index e6f1ac1b5d0d..e7bd25097ea5 100755 --- a/tools/objtool/tests/run-tests.sh +++ b/tools/objtool/tests/run-tests.sh @@ -31,13 +31,15 @@ A test may be named with or without its "test-" prefix and ".sh" suffix, and is looked for in both directories. Options: - -k, --keep do not delete each test's working directory; print its path, - so the objects a failing test built can be looked at + -k, --keep same as KEEP=all (see below) Environment: OBJTOOL objtool binary to test (default ../objtool) CC compiler used to build fixtures (default gcc) ARCH architecture the tests are for (default: uname -m) + KEEP failed keep only failing tests (default) + all keep every test's working directory + none remove all working directories A test which needs something of its own says so in its skip message. EOF @@ -46,16 +48,28 @@ EOF cd "$(dirname "$0")" || exit 1 +keep_from_args= while [ $# -gt 0 ]; do case "$1" in -h|--help) usage ;; - -k|--keep) export KLP_TEST_KEEP=1; shift ;; + -k|--keep) keep_from_args=all; shift ;; --) shift; break ;; -*) echo "unknown option: $1" >&2; usage 1 ;; *) break ;; esac done +KLP_TEST_KEEP="${KEEP:-failed}" +[ -n "$keep_from_args" ] && KLP_TEST_KEEP="$keep_from_args" +case "$KLP_TEST_KEEP" in +all|none|failed) ;; +*) + echo "invalid KEEP=$KLP_TEST_KEEP (want failed, all, or none)" >&2 + exit 1 + ;; +esac +export KLP_TEST_KEEP + echo "TAP version 13" # Sourcing the harness runs its preflight, which decides which architecture @@ -112,6 +126,7 @@ rundir="$(mktemp -d "${TMPDIR:-/tmp}/klp-tests.XXXXXXXX")" || echo "1..${#tests[@]}" pass=0 fail=0 static_skip=0 probe_skip=0 xfail=0 xpass=0 +failed_dirs=() for t in "${tests[@]}"; do out="$(KLP_TEST_WORKDIR="$rundir/${t%.sh}" ./"$t" 2>&1)" @@ -147,16 +162,16 @@ for t in "${tests[@]}"; do rest="$rest${rest:+$'\n'}was: $result" result="not ok - $(basename "$t" .sh): undeclared skip" result="$result (use gcc_only/clang_only or require_input_*)" - fail=$((fail + 1)) ;; + fail=$((fail + 1)); failed_dirs+=( "$rundir/${t%.sh}" ) ;; "not ok"*"# TODO"*) xfail=$((xfail + 1)) ;; - "ok"*"# TODO"*) xpass=$((xpass + 1)) ;; - "not ok"*) fail=$((fail + 1)) ;; + "ok"*"# TODO"*) xpass=$((xpass + 1)); failed_dirs+=( "$rundir/${t%.sh}" ) ;; + "not ok"*) fail=$((fail + 1)); failed_dirs+=( "$rundir/${t%.sh}" ) ;; "ok"*) pass=$((pass + 1)) ;; *) # No result line at all: the test died before reporting. rest="$rest${rest:+$'\n'}exited $rc without a result line" result="not ok - $(basename "$t" .sh): no TAP result" - fail=$((fail + 1)) ;; + fail=$((fail + 1)); failed_dirs+=( "$rundir/${t%.sh}" ) ;; esac echo "$result" @@ -167,15 +182,35 @@ done echo "# pass:$pass fail:$fail static-skip:$static_skip" \ "probe-skip:$probe_skip xfail:$xfail xpass:$xpass" -# A failure is the one time the objects matter, and by default they are -# already gone. Say so then rather than in the usage text nobody reads while -# something is broken. -if [ -n "${KLP_TEST_KEEP:-}" ]; then - echo "# working directories kept in $rundir -- inspect, then rm -rf it" -elif ! rmdir "$rundir"/*/ "$rundir" 2>/dev/null; then - echo "# $rundir was not empty; a test did not clean up after itself" -elif [ "$fail" != 0 ] || [ "$xpass" != 0 ]; then - echo "# re-run with --keep to hold on to what a failing test built" -fi +case "$KLP_TEST_KEEP" in +all) + echo "# keep=all: workdirs kept in $rundir" + echo "# inspect: diff.log, readelf -S out.o under each test-* subdirectory" + echo "# cleanup: rm -rf $rundir" + ;; +failed) + if [ "${#failed_dirs[@]}" -gt 0 ]; then + echo "# keep=failed: ${#failed_dirs[@]} failing test(s) kept under $rundir:" + for d in "${failed_dirs[@]}"; do + echo "# ${d#"$rundir"/}/" + done + echo "# inspect: diff.log readelf -S out.o" + echo "# one test: $PWD/run-tests.sh <name>" + echo "# cleanup: rm -rf $rundir" + elif ! rmdir "$rundir" 2>/dev/null; then + echo "# $rundir was not empty; a test did not clean up after itself" + fi + ;; +none) + if ! rmdir "$rundir"/*/ "$rundir" 2>/dev/null; then + if [ "$fail" != 0 ] || [ "$xpass" != 0 ]; then + echo "# keep=none: artifacts were removed" \ + "(re-run with KEEP=failed or KEEP=all)" + elif [ -d "$rundir" ]; then + echo "# $rundir was not empty; a test did not clean up after itself" + fi + fi + ;; +esac [ "$fail" = 0 ] && [ "$xpass" = 0 ] -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* Re: [PATCH 10/58] objtool: Keep failing test workdirs by default 2026-09-11 18:42 ` [PATCH 10/58] objtool: Keep failing test workdirs by default Song Liu @ 2026-09-11 19:07 ` sashiko-bot 0 siblings, 0 replies; 77+ messages in thread From: sashiko-bot @ 2026-09-11 19:07 UTC (permalink / raw) To: Song Liu, live-patching Cc: peterz, jikos, pmladek, joe.lawrence, puranjay, mbenes, jpoimboe, kernel-team Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Medium] Tests that fail due to unexpected termination (e.g., shell errors, signals) or undeclared skips have their working directories incorrectly deleted despite KEEP=failed, while the runner falsely claims they were kept. - [Medium] When a test is executed standalone (not via run-tests.sh) and fails, KEEP=failed silently leaks the temporary directory without printing its path. -- commit 14c7ac654e8638035a6ea9f8c26ee25df0293be4 Author: Joe Lawrence <joe.lawrence@redhat.com> objtool: Keep failing test workdirs by default Change the default behavior of the klp test runner to save failing test working directories instead of deleting them on failure. Provide a KEEP Makefile variable that can optionally retain 'all' or 'none' of those directories. > diff --git a/tools/objtool/tests/lib.sh b/tools/objtool/tests/lib.sh > index 4d8401ee8b412..6c607a0435023 100644 > --- a/tools/objtool/tests/lib.sh > +++ b/tools/objtool/tests/lib.sh [ ... ] > @@ -186,12 +187,14 @@ cleanup() > { > [ -n "$workdir" ] || return 0 > > - if [ -n "${KLP_TEST_KEEP:-}" ]; then > - [ -n "${KLP_TEST_WORKDIR:-}" ] || echo "# kept $workdir" > - return 0 > - fi > - > - rm -rf "$workdir" [Severity: Medium] When a test is executed standalone (not via run-tests.sh) and fails, does this change silently leak the temporary directory without printing its path? Before this patch, standalone test failures using KEEP=failed informed the user of the generated /tmp directory path. Without the print statement, developers might not be aware of the leaked directory location. > + case "${KLP_TEST_KEEP:-failed}" in > + all) return 0 ;; > + none) rm -rf "$workdir" ;; > + failed|*) > + [ -n "${KLP_TEST_FAILED:-}" ] && return 0 > + rm -rf "$workdir" [Severity: Medium] If a test script exits unexpectedly without setting KLP_TEST_FAILED=1 (e.g., syntax error, shell error, or signal), will this incorrectly delete the working directory despite KEEP=failed being set? > + ;; > + esac > } > > # setup [exported symbol...] > diff --git a/tools/objtool/tests/run-tests.sh b/tools/objtool/tests/run-tests.sh > index e6f1ac1b5d0da..e7bd25097ea58 100755 > --- a/tools/objtool/tests/run-tests.sh > +++ b/tools/objtool/tests/run-tests.sh [ ... ] > @@ -147,16 +162,16 @@ for t in "${tests[@]}"; do > rest="$rest${rest:+$'\n'}was: $result" > result="not ok - $(basename "$t" .sh): undeclared skip" > result="$result (use gcc_only/clang_only or require_input_*)" > - fail=$((fail + 1)) ;; > + fail=$((fail + 1)); failed_dirs+=( "$rundir/${t%.sh}" ) ;; > "not ok"*"# TODO"*) xfail=$((xfail + 1)) ;; > - "ok"*"# TODO"*) xpass=$((xpass + 1)) ;; > - "not ok"*) fail=$((fail + 1)) ;; > + "ok"*"# TODO"*) xpass=$((xpass + 1)); failed_dirs+=( "$rundir/${t%.sh}" ) ;; > + "not ok"*) fail=$((fail + 1)); failed_dirs+=( "$rundir/${t%.sh}" ) ;; > "ok"*) pass=$((pass + 1)) ;; > *) > # No result line at all: the test died before reporting. > rest="$rest${rest:+$'\n'}exited $rc without a result line" > result="not ok - $(basename "$t" .sh): no TAP result" > - fail=$((fail + 1)) ;; > + fail=$((fail + 1)); failed_dirs+=( "$rundir/${t%.sh}" ) ;; > esac > > echo "$result" [ ... ] > @@ -167,15 +182,35 @@ done > echo "# pass:$pass fail:$fail static-skip:$static_skip" \ > "probe-skip:$probe_skip xfail:$xfail xpass:$xpass" [ ... ] > +failed) > + if [ "${#failed_dirs[@]}" -gt 0 ]; then > + echo "# keep=failed: ${#failed_dirs[@]} failing test(s) kept under $rundir:" > + for d in "${failed_dirs[@]}"; do > + echo "# ${d#"$rundir"/}/" > + done [Severity: Medium] Will this instruct the user to inspect directories that have already been wiped? When a test crashes or skips without setting KLP_TEST_FAILED, the main loop in run-tests.sh correctly detects this as a failure and adds it to failed_dirs. However, cleanup() in lib.sh will have already deleted the directory because KLP_TEST_FAILED is empty, leading to confusing instructions for the user. > + echo "# inspect: diff.log readelf -S out.o" > + echo "# one test: $PWD/run-tests.sh <name>" > + echo "# cleanup: rm -rf $rundir" > + elif ! rmdir "$rundir" 2>/dev/null; then -- Sashiko AI review · https://sashiko.dev/#/patchset/20260911185031.1534046-1-song@kernel.org?part=10 ^ permalink raw reply [flat|nested] 77+ messages in thread
* [PATCH 11/58] objtool: Forward toolchain variables to the klp test runner 2026-09-11 18:42 [PATCH 00/58] Unit test framework for klp-build toolchain Song Liu ` (9 preceding siblings ...) 2026-09-11 18:42 ` [PATCH 10/58] objtool: Keep failing test workdirs by default Song Liu @ 2026-09-11 18:42 ` Song Liu 2026-09-11 18:42 ` [PATCH 12/58] objtool/klp: Add test for rejecting changed data Song Liu ` (15 subsequent siblings) 26 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:42 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu From: Joe Lawrence <joe.lawrence@redhat.com> The tests target already passes ARCH and CROSS_COMPILE into run-tests.sh. lib.sh uses those to pick a default compiler and binutils: CC=${CC:-${CROSS_COMPILE}gcc} LD=${LD:-${CROSS_COMPILE}ld} READELF=${READELF:-${CROSS_COMPILE}readelf} OBJCOPY=${OBJCOPY:-${CROSS_COMPILE}objcopy} Those defaults only apply when the variables reach the shell that sources lib.sh. The recipe passed OBJTOOL, ARCH and CROSS_COMPILE explicitly, but not CC or the binutils. Makefile-assigned values (e.g. from LLVM=1) are not visible in the recipe environment unless forwarded: $ LLVM=1 make -C tools/objtool tests ... # preflight # compiler gcc (GCC) ... Pass CC, LD, READELF and OBJCOPY the same way as ARCH and CROSS_COMPILE, so fixtures are built and inspected with the toolchain make selected: $ LLVM=1 make -C tools/objtool tests ... # preflight # compiler clang version 21.1.8 ... Signed-off-by: Joe Lawrence <joe.lawrence@redhat.com> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- tools/objtool/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/objtool/Makefile b/tools/objtool/Makefile index ed5cf146ef2e..4429ae6db888 100644 --- a/tools/objtool/Makefile +++ b/tools/objtool/Makefile @@ -153,6 +153,7 @@ mrproper: clean tests: $(OBJTOOL) $(Q)OBJTOOL=$(abspath $(OBJTOOL)) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) \ + CC='$(CC)' LD='$(LD)' READELF='$(READELF)' OBJCOPY='$(OBJCOPY)' \ KEEP=$(KEEP) $(srctree)/tools/objtool/tests/run-tests.sh FORCE: -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 12/58] objtool/klp: Add test for rejecting changed data 2026-09-11 18:42 [PATCH 00/58] Unit test framework for klp-build toolchain Song Liu ` (10 preceding siblings ...) 2026-09-11 18:42 ` [PATCH 11/58] objtool: Forward toolchain variables to the klp test runner Song Liu @ 2026-09-11 18:42 ` Song Liu 2026-09-11 18:42 ` [PATCH 13/58] objtool/klp: Add test for newly introduced data Song Liu ` (14 subsequent siblings) 26 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:42 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu From: Puranjay Mohan <puranjay@kernel.org> Livepatching replaces functions. Nothing can swap a variable which live code already refers to, so a patch which changes one has to be refused rather than applied with the old value left in place. Signed-off-by: Puranjay Mohan <puranjay@kernel.org> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../tests/generic/fixtures/changed_data.c | 16 ++++++++++++++++ .../objtool/tests/generic/test-changed-data.sh | 18 ++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/changed_data.c create mode 100755 tools/objtool/tests/generic/test-changed-data.sh diff --git a/tools/objtool/tests/generic/fixtures/changed_data.c b/tools/objtool/tests/generic/fixtures/changed_data.c new file mode 100644 index 000000000000..b52461835444 --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/changed_data.c @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Data whose value differs between the two builds. */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +#ifdef PATCHED +int klp_test_data = 2; +#else +int klp_test_data = 1; +#endif + +int target(int x) +{ + return x + klp_test_data; +} diff --git a/tools/objtool/tests/generic/test-changed-data.sh b/tools/objtool/tests/generic/test-changed-data.sh new file mode 100755 index 000000000000..c5c7381bb409 --- /dev/null +++ b/tools/objtool/tests/generic/test-changed-data.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Livepatching replaces functions, not data. A changed data symbol must be +# rejected. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair changed_data.c +run_diff 255 + +diff_log | grep -q 'changed data: klp_test_data' || + fail "expected rejection, got: $(diff_log | tail -1)" +[ -e "$workdir/out.o" ] && + fail "output object produced for a rejected input" + +pass "changed data symbol rejected" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 13/58] objtool/klp: Add test for newly introduced data 2026-09-11 18:42 [PATCH 00/58] Unit test framework for klp-build toolchain Song Liu ` (11 preceding siblings ...) 2026-09-11 18:42 ` [PATCH 12/58] objtool/klp: Add test for rejecting changed data Song Liu @ 2026-09-11 18:42 ` Song Liu 2026-09-11 19:07 ` sashiko-bot 2026-09-11 18:42 ` [PATCH 14/58] objtool/klp: Add test for newly introduced functions Song Liu ` (13 subsequent siblings) 26 siblings, 1 reply; 77+ messages in thread From: Song Liu @ 2026-09-11 18:42 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu From: Puranjay Mohan <puranjay@kernel.org> Adding data differs from changing it: nothing in the running kernel refers to a new variable, so it is safe and has to travel into the livepatch with the function using it. Signed-off-by: Puranjay Mohan <puranjay@kernel.org> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../objtool/tests/generic/fixtures/new_data.c | 18 ++++++++++++++++++ tools/objtool/tests/generic/test-new-data.sh | 17 +++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/new_data.c create mode 100755 tools/objtool/tests/generic/test-new-data.sh diff --git a/tools/objtool/tests/generic/fixtures/new_data.c b/tools/objtool/tests/generic/fixtures/new_data.c new file mode 100644 index 000000000000..c82770b126fe --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/new_data.c @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Data introduced by the patch. */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +#ifdef PATCHED +static const int klp_new_data[4] = { 1, 2, 3, 4 }; +#endif + +int target(int x) +{ +#ifdef PATCHED + return x + klp_new_data[x & 3]; +#else + return x; +#endif +} diff --git a/tools/objtool/tests/generic/test-new-data.sh b/tools/objtool/tests/generic/test-new-data.sh new file mode 100755 index 000000000000..83b72125b1e9 --- /dev/null +++ b/tools/objtool/tests/generic/test-new-data.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Data added by the patch has no counterpart in the running kernel and must be +# carried into the livepatch. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair new_data.c +run_diff + +assert_patched target +out_symbols | grep -q 'klp_new_data' || + fail "new data was not carried into the patch" + +pass "new data carried into the patch" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* Re: [PATCH 13/58] objtool/klp: Add test for newly introduced data 2026-09-11 18:42 ` [PATCH 13/58] objtool/klp: Add test for newly introduced data Song Liu @ 2026-09-11 19:07 ` sashiko-bot 0 siblings, 0 replies; 77+ messages in thread From: sashiko-bot @ 2026-09-11 19:07 UTC (permalink / raw) To: Song Liu, live-patching Cc: peterz, jikos, puranjay, kernel-team, pmladek, joe.lawrence, jpoimboe, mbenes Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] Test fixture 'new_data.c' defines a 'static const' array without preventing compiler optimization, which can lead to spurious test failures if folded. Additionally, 'test-new-data.sh' fails to assert the input premise. -- commit 6effea04700600e9c40a587a9beb3f3d63a133a2 Author: Puranjay Mohan <puranjay@kernel.org> objtool/klp: Add test for newly introduced data Adding data differs from changing it: nothing in the running kernel refers to a new variable, so it is safe and has to travel into the livepatch with the function using it. > diff --git a/tools/objtool/tests/generic/fixtures/new_data.c b/tools/objtool/tests/generic/fixtures/new_data.c > new file mode 100644 > index 0000000000000..c82770b126fe6 > --- /dev/null > +++ b/tools/objtool/tests/generic/fixtures/new_data.c > @@ -0,0 +1,18 @@ [ ... ] > +static const char __modinfo[] > + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; > + > +#ifdef PATCHED > +static const int klp_new_data[4] = { 1, 2, 3, 4 }; [Severity: Medium] Will this small array be optimized away by the compiler? If the compiler folds the array lookup into a select statement or arithmetic, the symbol might not be emitted into the patched object file, leading to spurious test suite failures. Could this use __attribute__((used)) or an inline assembly barrier to prevent read folding? > +#endif > + > +int target(int x) > +{ [ ... ] > diff --git a/tools/objtool/tests/generic/test-new-data.sh b/tools/objtool/tests/generic/test-new-data.sh > new file mode 100755 > index 0000000000000..83b72125b1e96 > --- /dev/null > +++ b/tools/objtool/tests/generic/test-new-data.sh > @@ -0,0 +1,17 @@ [ ... ] > +assert_patched target > +out_symbols | grep -q 'klp_new_data' || > + fail "new data was not carried into the patch" [Severity: Medium] Could this assertion fail incorrectly if the compiler optimized the symbol away? Should there be a preceding assert_input_symbol klp_new_data to ensure the symbol was actually emitted in patched.o, avoiding violations of the objtool test writing guidelines? > + > +pass "new data carried into the patch" -- Sashiko AI review · https://sashiko.dev/#/patchset/20260911185031.1534046-1-song@kernel.org?part=13 ^ permalink raw reply [flat|nested] 77+ messages in thread
* [PATCH 14/58] objtool/klp: Add test for newly introduced functions 2026-09-11 18:42 [PATCH 00/58] Unit test framework for klp-build toolchain Song Liu ` (12 preceding siblings ...) 2026-09-11 18:42 ` [PATCH 13/58] objtool/klp: Add test for newly introduced data Song Liu @ 2026-09-11 18:42 ` Song Liu 2026-09-11 18:42 ` [PATCH 15/58] objtool/klp: Add test for static local correlation Song Liu ` (12 subsequent siblings) 26 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:42 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu From: Puranjay Mohan <puranjay@kernel.org> A function added by the patch has no original to correlate against, and still has to be carried into the livepatch or the changed caller ends up referencing something which does not exist. The helper is noinline so the case survives the optimiser; otherwise the compiler folds it into its only caller and the test covers nothing. Signed-off-by: Puranjay Mohan <puranjay@kernel.org> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../tests/generic/fixtures/new_function.c | 21 +++++++++++++++++++ .../tests/generic/test-new-function.sh | 17 +++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/new_function.c create mode 100755 tools/objtool/tests/generic/test-new-function.sh diff --git a/tools/objtool/tests/generic/fixtures/new_function.c b/tools/objtool/tests/generic/fixtures/new_function.c new file mode 100644 index 000000000000..e7886eaaff3e --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/new_function.c @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Function introduced by the patch. noinline keeps it from being folded. */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +#ifdef PATCHED +static __attribute__((noinline)) int klp_new_helper(int x) +{ + return x * 7; +} +#endif + +int target(int x) +{ +#ifdef PATCHED + return klp_new_helper(x); +#else + return x; +#endif +} diff --git a/tools/objtool/tests/generic/test-new-function.sh b/tools/objtool/tests/generic/test-new-function.sh new file mode 100755 index 000000000000..17af18545b7d --- /dev/null +++ b/tools/objtool/tests/generic/test-new-function.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# A function added by the patch has no original to correlate against and must +# still be carried into the livepatch. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair new_function.c +run_diff + +assert_patched target +out_symbols | grep -q 'klp_new_helper' || + fail "new function was not carried into the patch" + +pass "new function carried into the patch with its caller" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 15/58] objtool/klp: Add test for static local correlation 2026-09-11 18:42 [PATCH 00/58] Unit test framework for klp-build toolchain Song Liu ` (13 preceding siblings ...) 2026-09-11 18:42 ` [PATCH 14/58] objtool/klp: Add test for newly introduced functions Song Liu @ 2026-09-11 18:42 ` Song Liu 2026-09-11 18:42 ` [PATCH 16/58] objtool/klp: Add test for cold function halves Song Liu ` (11 subsequent siblings) 26 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:42 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu From: Puranjay Mohan <puranjay@kernel.org> A static local must be correlated with its original rather than duplicated. The replacement has to reach the existing variable through a klp relocation; a fresh definition would discard whatever state the running kernel accumulated. Signed-off-by: Puranjay Mohan <puranjay@kernel.org> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../tests/generic/fixtures/static_local.c | 17 +++++++++++++ .../tests/generic/test-static-local.sh | 24 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/static_local.c create mode 100755 tools/objtool/tests/generic/test-static-local.sh diff --git a/tools/objtool/tests/generic/fixtures/static_local.c b/tools/objtool/tests/generic/fixtures/static_local.c new file mode 100644 index 000000000000..f2f025d00a39 --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/static_local.c @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Static local in a patched function. */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +int target(int x) +{ + static int counter; + + counter += 1; +#ifdef PATCHED + return x + counter + 1; +#else + return x + counter; +#endif +} diff --git a/tools/objtool/tests/generic/test-static-local.sh b/tools/objtool/tests/generic/test-static-local.sh new file mode 100755 index 000000000000..d57efa64dfd9 --- /dev/null +++ b/tools/objtool/tests/generic/test-static-local.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# A static local must be correlated with the original, not duplicated: a second +# copy would discard the state the running kernel accumulated. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair static_local.c + +in_symbols orig.o | grep -q 'counter' || + probe_skip "compiler emitted no distinct static local symbol" + +run_diff +assert_patched target + +out_symbols | grep -q '\.klp\.sym\..*\.counter' || + fail "static local not referenced through a klp relocation" + +out_symbols | grep 'counter' | grep -qvE 'UND|\.klp\.(sym|tombstone)' && + fail "static local was given a fresh definition" + +pass "static local correlated rather than duplicated" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 16/58] objtool/klp: Add test for cold function halves 2026-09-11 18:42 [PATCH 00/58] Unit test framework for klp-build toolchain Song Liu ` (14 preceding siblings ...) 2026-09-11 18:42 ` [PATCH 15/58] objtool/klp: Add test for static local correlation Song Liu @ 2026-09-11 18:42 ` Song Liu 2026-09-11 19:07 ` sashiko-bot 2026-09-11 18:42 ` [PATCH 17/58] objtool/klp: Add test for special section extraction Song Liu ` (10 subsequent siblings) 26 siblings, 1 reply; 77+ messages in thread From: Song Liu @ 2026-09-11 18:42 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu From: Puranjay Mohan <puranjay@kernel.org> The compiler splits unlikely code into a separate foo.cold symbol. Both halves are the same function and both belong in the livepatch: carrying only the hot part leaves the cold path branching into unpatched code. GCC needs -freorder-blocks-and-partition to split reliably. The flag is probed rather than assumed, and the test skips when the compiler declines to split at all. Signed-off-by: Puranjay Mohan <puranjay@kernel.org> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../tests/generic/fixtures/cold_function.c | 21 +++++++++++++ .../tests/generic/test-cold-function.sh | 30 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/cold_function.c create mode 100755 tools/objtool/tests/generic/test-cold-function.sh diff --git a/tools/objtool/tests/generic/fixtures/cold_function.c b/tools/objtool/tests/generic/fixtures/cold_function.c new file mode 100644 index 000000000000..f6d410983ce2 --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/cold_function.c @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Function the compiler may split into a hot part and a foo.cold part. */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +static void __attribute__((cold, noinline)) slow_path(int x) +{ + __asm__ volatile("" :: "r"(x)); +} + +int target(int x) +{ + if (__builtin_expect(x < 0, 0)) + slow_path(x); +#ifdef PATCHED + return x + 2; +#else + return x + 1; +#endif +} diff --git a/tools/objtool/tests/generic/test-cold-function.sh b/tools/objtool/tests/generic/test-cold-function.sh new file mode 100755 index 000000000000..315f407d9ad7 --- /dev/null +++ b/tools/objtool/tests/generic/test-cold-function.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Both halves of a split function belong to the patch; carrying only the hot +# part leaves the cold path branching into unpatched code. + +. "$(dirname "$0")/../lib.sh" + +# Clang does not split functions into a cold part at all, so there is nothing +# for this test to look at there. A given gcc may or may not split, which is a +# version property rather than a compiler choice -- that stays a probe below. +gcc_only "clang does not split functions into a cold part" + +setup + +split_flag=-freorder-blocks-and-partition +cc_supports "$split_flag" || split_flag= + +build_pair cold_function.c $split_flag + +in_symbols orig.o | grep -qE 'target\.cold' || + probe_skip "compiler did not split the function into a cold part" + +run_diff + +assert_patched target +out_symbols | grep -qE 'target\.cold' || + fail "cold half was not carried into the patch" + +pass "cold half carried into the patch with its parent" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* Re: [PATCH 16/58] objtool/klp: Add test for cold function halves 2026-09-11 18:42 ` [PATCH 16/58] objtool/klp: Add test for cold function halves Song Liu @ 2026-09-11 19:07 ` sashiko-bot 0 siblings, 0 replies; 77+ messages in thread From: sashiko-bot @ 2026-09-11 19:07 UTC (permalink / raw) To: Song Liu, live-patching Cc: peterz, kernel-team, pmladek, jikos, jpoimboe, mbenes, puranjay, joe.lawrence Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] The test script uses an unanchored regex that will spuriously pass if the tool fails to carry the cold half into the patch, masking the exact regression it intends to prevent. -- commit d68806991ec6b4cf8d30b1b6025cc1b8ab0a7deb Author: Puranjay Mohan <puranjay@kernel.org> objtool/klp: Add test for cold function halves The compiler splits unlikely code into a separate foo.cold symbol. GCC needs -freorder-blocks-and-partition to split reliably, which is probed rather than assumed. This test verifies that both halves of the split function are carried into the livepatch. > diff --git a/tools/objtool/tests/generic/test-cold-function.sh b/tools/objtool/tests/generic/test-cold-function.sh > new file mode 100755 > index 0000000000000..315f407d9ad70 > --- /dev/null > +++ b/tools/objtool/tests/generic/test-cold-function.sh > @@ -0,0 +1,30 @@ [ ... ] > +run_diff > + > +assert_patched target > +out_symbols | grep -qE 'target\.cold' || > + fail "cold half was not carried into the patch" [Severity: Medium] Does this unanchored regex risk masking the very regression this test is designed to prevent? If the tool regresses and erroneously fails to copy the cold half into the patched object, the hot half (target) will contain an unresolved branch to it. The tool might resolve this branch by generating an undefined livepatch symbol (e.g., .klp.sym.vmlinux.target.cold,0) or a standard undefined reference. Because the regex target\.cold is unanchored and does not check the symbol's type, binding, or section, the grep will match this unresolved symbol name, causing the test to incorrectly report success. > + > +pass "cold half carried into the patch with its parent" -- Sashiko AI review · https://sashiko.dev/#/patchset/20260911185031.1534046-1-song@kernel.org?part=16 ^ permalink raw reply [flat|nested] 77+ messages in thread
* [PATCH 17/58] objtool/klp: Add test for special section extraction 2026-09-11 18:42 [PATCH 00/58] Unit test framework for klp-build toolchain Song Liu ` (15 preceding siblings ...) 2026-09-11 18:42 ` [PATCH 16/58] objtool/klp: Add test for cold function halves Song Liu @ 2026-09-11 18:42 ` Song Liu 2026-09-11 18:42 ` [PATCH 18/58] objtool/klp: Add test for selective " Song Liu ` (9 subsequent siblings) 26 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:42 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu From: Puranjay Mohan <puranjay@kernel.org> create_fake_symbols() gives each special section entry a symbol so entries can be extracted individually. Entries with ANNOTATE_DATA_SPECIAL are handled first; the rest have their boundaries derived from the entry or relocation size. The second pass has to key off whether the first one created symbols, not off whether the section already has something at offset 0. Clang puts an assembler-local label at the start of .kcfi_traps, and treating that as already handled means nothing is extracted: klp diff still reports the changed function and succeeds, but the special section is missing from the module. The fixture reproduces the shape without needing CFI or x86. Signed-off-by: Puranjay Mohan <puranjay@kernel.org> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../tests/generic/fixtures/special_section.c | 24 +++++++++++++++++++ .../tests/generic/test-special-section.sh | 20 ++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/special_section.c create mode 100755 tools/objtool/tests/generic/test-special-section.sh diff --git a/tools/objtool/tests/generic/fixtures/special_section.c b/tools/objtool/tests/generic/fixtures/special_section.c new file mode 100644 index 000000000000..d28c5541e337 --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/special_section.c @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Special section entry with no ANNOTATE_DATA_SPECIAL annotation and a local + * label at offset 0, the shape Clang produces for .kcfi_traps. + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +int target(int x) +{ + asm volatile( + "1:\n\t" + ".pushsection .kcfi_traps, \"a\"\n\t" + ".balign 4\n\t" + "trap_marker:\n\t" + ".long 1b - .\n\t" + ".popsection\n\t"); +#ifdef PATCHED + return x + 2; +#else + return x + 1; +#endif +} diff --git a/tools/objtool/tests/generic/test-special-section.sh b/tools/objtool/tests/generic/test-special-section.sh new file mode 100755 index 000000000000..b6a9139c0062 --- /dev/null +++ b/tools/objtool/tests/generic/test-special-section.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# A .kcfi_traps entry belonging to a patched function must be extracted even +# without ANNOTATE_DATA_SPECIAL and with a local label already at offset 0. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair special_section.c + +in_symbols orig.o | grep -q 'trap_marker' || + probe_skip "fixture produced no .kcfi_traps on this arch" + +run_diff + +assert_patched target +assert_section ".kcfi_traps" + +pass ".kcfi_traps extracted despite a local label at offset 0" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 18/58] objtool/klp: Add test for selective special section extraction 2026-09-11 18:42 [PATCH 00/58] Unit test framework for klp-build toolchain Song Liu ` (16 preceding siblings ...) 2026-09-11 18:42 ` [PATCH 17/58] objtool/klp: Add test for special section extraction Song Liu @ 2026-09-11 18:42 ` Song Liu 2026-09-11 18:42 ` [PATCH 19/58] objtool/klp: Add test for jump table key relocations Song Liu ` (8 subsequent siblings) 26 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:42 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu From: Puranjay Mohan <puranjay@kernel.org> Two functions contribute entries to one special section but only one is patched. Cloning the neighbouring entry drags in whatever it points at, which is how a livepatch ends up holding relocations against unrelated code. Signed-off-by: Puranjay Mohan <puranjay@kernel.org> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../generic/fixtures/special_section_shared.c | 31 +++++++++++++++++++ .../generic/test-special-section-shared.sh | 26 ++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/special_section_shared.c create mode 100755 tools/objtool/tests/generic/test-special-section-shared.sh diff --git a/tools/objtool/tests/generic/fixtures/special_section_shared.c b/tools/objtool/tests/generic/fixtures/special_section_shared.c new file mode 100644 index 000000000000..f54e24f862c6 --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/special_section_shared.c @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Two functions contribute to one special section; only one is patched. */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +int other(int x) +{ + asm volatile( + "2:\n\t" + ".pushsection .kcfi_traps, \"a\"\n\t" + ".balign 4\n\t" + ".long 2b - .\n\t" + ".popsection\n\t"); + return x * 5; +} + +int target(int x) +{ + asm volatile( + "1:\n\t" + ".pushsection .kcfi_traps, \"a\"\n\t" + ".balign 4\n\t" + ".long 1b - .\n\t" + ".popsection\n\t"); +#ifdef PATCHED + return x + 2; +#else + return x + 1; +#endif +} diff --git a/tools/objtool/tests/generic/test-special-section-shared.sh b/tools/objtool/tests/generic/test-special-section-shared.sh new file mode 100755 index 000000000000..05c6110b6aef --- /dev/null +++ b/tools/objtool/tests/generic/test-special-section-shared.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Only the patched function's special section entry may be extracted. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair special_section_shared.c + +has_input_section orig.o .kcfi_traps || + probe_skip "fixture produced no .kcfi_traps on this arch" + +run_diff + +assert_patched target +assert_not_patched other +assert_section ".kcfi_traps" + +entries="$(out_relocs | awk '/rela\.kcfi_traps/,/^$/' | grep -c 'target')" +[ "$entries" = 1 ] || fail "expected one .kcfi_traps entry, found $entries" + +out_relocs | awk '/rela\.kcfi_traps/,/^$/' | grep -q 'other' && + fail "the untouched function's entry was dragged in" + +pass "only the patched function's entry extracted" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 19/58] objtool/klp: Add test for jump table key relocations 2026-09-11 18:42 [PATCH 00/58] Unit test framework for klp-build toolchain Song Liu ` (17 preceding siblings ...) 2026-09-11 18:42 ` [PATCH 18/58] objtool/klp: Add test for selective " Song Liu @ 2026-09-11 18:42 ` Song Liu 2026-09-11 18:42 ` [PATCH 20/58] objtool/klp: Add test for rejecting module-owned static branch keys Song Liu ` (7 subsequent siblings) 26 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:42 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu From: Puranjay Mohan <puranjay@kernel.org> A cloned __jump_table entry must keep a relocation in its key slot. objtool parses the table again after klp diff to convert the static branch's scaffold instruction, and an empty slot leaves it unable to do so. The scaffold then stays as emitted and the module trips BUG() in __jump_label_patch() once the key's state differs from its compile-time default. Check both halves for a vmlinux-owned key: unexported gives a tombstone plus a klp relocation, exported gives an ordinary relocation and no klp machinery. The fixture defines the key as an STT_OBJECT, which is what the kernel emits. validate_special_section_klp_reloc() ignores anything else, so a fixture using an undefined extern would skip the interesting paths. Signed-off-by: Puranjay Mohan <puranjay@kernel.org> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../tests/generic/fixtures/jump_label.c | 76 +++++++++++++++++++ .../tests/generic/test-jump-label-key.sh | 44 +++++++++++ 2 files changed, 120 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/jump_label.c create mode 100755 tools/objtool/tests/generic/test-jump-label-key.sh diff --git a/tools/objtool/tests/generic/fixtures/jump_label.c b/tools/objtool/tests/generic/fixtures/jump_label.c new file mode 100644 index 000000000000..ecd3c06912af --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/jump_label.c @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Static branch in a patched function. The jump table entry is written out by + * hand, mirroring JUMP_TABLE_ENTRY(), so the fixture builds without kernel + * headers. The key is an STT_OBJECT; anything else is ignored by + * validate_special_section_klp_reloc(). + * + * MODNAME selects whether the key is taken to belong to vmlinux or a module. + * + * NEW_KEY puts the whole static branch behind PATCHED, so the patch introduces + * one where the original had none -- a different question from patching code + * that already has a key, because the __jump_table entry itself is new. + * + * KEY_NAME renames the key. Two names are special to + * validate_special_section_klp_reloc(): a __tracepoint_* key and the + * __UNIQUE_ID_ddebug_* one pr_debug() generates are both unsupported in a + * module, but are disabled with a warning rather than rejected, because the + * kernel is full of them and refusing outright would make ordinary functions + * unpatchable. + * + * STATIC_KEY makes the key file-local. That changes the shape of the + * relocation rather than the meaning of the code: a reference to a static lands + * on the section symbol plus an addend, so the key has to be resolved from the + * section before it can be recognised as a key at all. + */ + +#ifndef MODNAME +#define MODNAME "vmlinux" +#endif + +#ifndef KEY_NAME +#define KEY_NAME klp_test_key +#endif + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=" MODNAME; + +#ifdef STATIC_KEY +static long KEY_NAME; +#else +long KEY_NAME; +#endif + +int target(int x) +{ + int r = x; + +#if defined(NEW_KEY) && !defined(PATCHED) + /* The original has no static branch at all. */ + return r + 1; +#else + asm goto( + "1: nop\n\t" + ".pushsection __jump_table, \"aw\"\n\t" + ".balign 8\n\t" + "912:\n\t" + ".pushsection .discard.annotate_data, \"M\", @progbits, 8\n\t" + ".long 912b - ., 1\n\t" + ".popsection\n\t" + ".long 1b - ., %l[l_yes] - .\n\t" + ".quad %c0 - .\n\t" + ".popsection\n\t" + : : "i" (&KEY_NAME) : : l_yes); + + r += 1; + goto out; +l_yes: + r += 2; +out: +#endif +#ifdef PATCHED + return r + 100; +#else + return r; +#endif +} diff --git a/tools/objtool/tests/generic/test-jump-label-key.sh b/tools/objtool/tests/generic/test-jump-label-key.sh new file mode 100755 index 000000000000..142f94bd38ec --- /dev/null +++ b/tools/objtool/tests/generic/test-jump-label-key.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# A cloned __jump_table entry must keep a relocation in its key slot, whether +# the key needs a klp relocation or not. + +. "$(dirname "$0")/../lib.sh" + +key=klp_test_key + +setup +build_pair jump_label.c + +has_input_section orig.o __jump_table || + probe_skip "fixture produced no __jump_table on this arch" + +key_slot_relocs() +{ + out_relocs | awk '/rela__jump_table/,/^$/' | grep -c "^0*8[[:space:]]" +} + +# Unexported: klp relocation, key slot holds a tombstone. +export_syms +run_diff + +[ "$(key_slot_relocs)" = 1 ] || + fail "unexported key: key slot has no relocation" +out_relocs | awk '/rela__jump_table/,/^$/' | grep -q "\.klp\.tombstone\.$key" || + fail "unexported key: expected a .klp.tombstone.$key relocation" +out_symbols | grep -q "\.klp\.sym\..*\.$key," || + fail "unexported key: no .klp.sym reference for the real relocation" + +# Exported: ordinary relocation, no klp machinery. +export_syms "$key" +run_diff + +[ "$(key_slot_relocs)" = 1 ] || + fail "exported key: key slot has no relocation" +out_relocs | awk '/rela__jump_table/,/^$/' | grep -q "[[:space:]]$key[[:space:]]*+" || + fail "exported key: expected a direct relocation to $key" +out_symbols | grep -q '\.klp\.tombstone\.' && + fail "exported key: tombstone emitted for an exported symbol" + +pass "key slot populated for exported and unexported vmlinux keys" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 20/58] objtool/klp: Add test for rejecting module-owned static branch keys 2026-09-11 18:42 [PATCH 00/58] Unit test framework for klp-build toolchain Song Liu ` (18 preceding siblings ...) 2026-09-11 18:42 ` [PATCH 19/58] objtool/klp: Add test for jump table key relocations Song Liu @ 2026-09-11 18:42 ` Song Liu 2026-09-11 18:42 ` [PATCH 21/58] objtool/klp: Add test for rejecting module-owned static call keys Song Liu ` (6 subsequent siblings) 26 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:42 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu From: Puranjay Mohan <puranjay@kernel.org> A static branch key belonging to a module cannot be reached with a klp relocation: the patch may be applied after that module's static branch init has run, which corrupts the code. Emitting one anyway produces a module which loads and then BUG()s the first time the key changes state, so klp diff has to refuse it. The fixture differs from the accepted case only in the module name recorded in .modinfo. Signed-off-by: Puranjay Mohan <puranjay@kernel.org> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../generic/test-jump-label-module-key.sh | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100755 tools/objtool/tests/generic/test-jump-label-module-key.sh diff --git a/tools/objtool/tests/generic/test-jump-label-module-key.sh b/tools/objtool/tests/generic/test-jump-label-module-key.sh new file mode 100755 index 000000000000..7c3f82cdd3c1 --- /dev/null +++ b/tools/objtool/tests/generic/test-jump-label-module-key.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# A static branch key owned by a module cannot be reached with a klp +# relocation and must be rejected. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair jump_label.c -DMODNAME='"klp_testmod"' + +has_input_section orig.o __jump_table || + probe_skip "fixture produced no __jump_table on this arch" + +run_diff 255 + +diff_log | grep -q 'unsupported static branch key klp_test_key' || + fail "expected rejection, got: $(diff_log | tail -1)" +[ -e "$workdir/out.o" ] && + fail "output object produced for a rejected input" + +pass "module-owned static branch key rejected" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 21/58] objtool/klp: Add test for rejecting module-owned static call keys 2026-09-11 18:42 [PATCH 00/58] Unit test framework for klp-build toolchain Song Liu ` (19 preceding siblings ...) 2026-09-11 18:42 ` [PATCH 20/58] objtool/klp: Add test for rejecting module-owned static branch keys Song Liu @ 2026-09-11 18:42 ` Song Liu 2026-09-11 18:42 ` [PATCH 22/58] objtool/klp: Add test for symids in discarded sections Song Liu ` (5 subsequent siblings) 26 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:42 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu From: Puranjay Mohan <puranjay@kernel.org> Static calls carry the same constraint as static branches. Check that a vmlinux-owned key is accepted and a module-owned one is refused. Signed-off-by: Puranjay Mohan <puranjay@kernel.org> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../tests/generic/fixtures/static_call.c | 53 +++++++++++++++++++ .../generic/test-static-call-module-key.sh | 25 +++++++++ 2 files changed, 78 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/static_call.c create mode 100755 tools/objtool/tests/generic/test-static-call-module-key.sh diff --git a/tools/objtool/tests/generic/fixtures/static_call.c b/tools/objtool/tests/generic/fixtures/static_call.c new file mode 100644 index 000000000000..5975986c92eb --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/static_call.c @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Static call site in a patched function, laid out by hand as for + * jump_label.c. MODNAME selects whether the key belongs to vmlinux or a + * module. + * + * NO_ANNOTATE drops the ANNOTATE_DATA_SPECIAL block from the patched build, + * leaving .static_call_sites with no annotation to describe its entry + * boundaries. The section carries no entsize either, so klp diff has to fall + * back on the annotations it can still see -- and when the patched object is + * the only one that lost them, the two sides disagree about how the section is + * divided up. + * + * NEW_CALL puts the call site behind PATCHED, so the patch introduces one + * where the original had none. The .static_call_sites entry is then new, with + * nothing in the original to correlate it against. + */ + +#ifndef MODNAME +#define MODNAME "vmlinux" +#endif + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=" MODNAME; + +long __SCK__klp_test_call; + +int target(int x) +{ +#if defined(NEW_CALL) && !defined(PATCHED) + /* The original has no static call at all. */ + return x + 1; +#else + __asm__ volatile( + "1: nop\n\t" + ".pushsection .static_call_sites, \"aw\"\n\t" + ".balign 8\n\t" + "912:\n\t" +#if !(defined(PATCHED) && defined(NO_ANNOTATE)) + ".pushsection .discard.annotate_data, \"M\", @progbits, 8\n\t" + ".long 912b - ., 1\n\t" + ".popsection\n\t" +#endif + ".long 1b - ., %c0 - .\n\t" + ".popsection\n\t" + :: "i" (&__SCK__klp_test_call)); +#endif +#ifdef PATCHED + return x + 2; +#else + return x + 1; +#endif +} diff --git a/tools/objtool/tests/generic/test-static-call-module-key.sh b/tools/objtool/tests/generic/test-static-call-module-key.sh new file mode 100755 index 000000000000..9ab373e90e10 --- /dev/null +++ b/tools/objtool/tests/generic/test-static-call-module-key.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# As for static branches, a static call key owned by a module must be rejected +# while a vmlinux-owned one is accepted. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair static_call.c + +has_input_section orig.o .static_call_sites || + probe_skip "fixture produced no .static_call_sites on this arch" + +run_diff +assert_patched target + +rm -f "$workdir/out.o" +build_pair static_call.c -DMODNAME='"klp_testmod"' +run_diff 255 + +diff_log | grep -q 'unsupported static call key __SCK__klp_test_call' || + fail "expected rejection, got: $(diff_log | tail -1)" + +pass "module-owned static call key rejected, vmlinux-owned accepted" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 22/58] objtool/klp: Add test for symids in discarded sections 2026-09-11 18:42 [PATCH 00/58] Unit test framework for klp-build toolchain Song Liu ` (20 preceding siblings ...) 2026-09-11 18:42 ` [PATCH 21/58] objtool/klp: Add test for rejecting module-owned static call keys Song Liu @ 2026-09-11 18:42 ` Song Liu 2026-09-11 18:42 ` [PATCH 23/58] objtool/klp: Add test for rejecting references to init code/data Song Liu ` (4 subsequent siblings) 26 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:42 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu From: Puranjay Mohan <puranjay@kernel.org> .klp.symid records duplicate-named locals so klp diff can work out their sympos. Symbols in sections the vmlinux link throws away have to be left out, or the table references symbols which no longer exist and the link fails: `__exitcall_foo' referenced in section `.klp.symid' of vmlinux.o: defined in discarded section `.exitcall.exit' of vmlinux.o Two translation units are compiled from one fixture and partially linked so the result has duplicate locals, which symid_needed() requires. One duplicate is in a live section and one in .exitcall.exit. Checking the live duplicate as well keeps the test honest: it would otherwise pass just as happily if symid generation stopped working entirely. Signed-off-by: Puranjay Mohan <puranjay@kernel.org> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../tests/generic/fixtures/symid_discarded.c | 25 +++++++++++ .../tests/generic/test-symid-discarded.sh | 44 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/symid_discarded.c create mode 100755 tools/objtool/tests/generic/test-symid-discarded.sh diff --git a/tools/objtool/tests/generic/fixtures/symid_discarded.c b/tools/objtool/tests/generic/fixtures/symid_discarded.c new file mode 100644 index 000000000000..573cc2d4474b --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/symid_discarded.c @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Compiled twice and partially linked so the result has duplicate locals, + * which is what symid_needed() requires. dup_normal is in a live section, + * dup_discarded in one the vmlinux link throws away. DISCARDED_SEC selects + * which discarded section, since there is more than one and each was its own + * bug. + */ + +#ifndef DISCARDED_SEC +#define DISCARDED_SEC ".exitcall.exit" +#endif + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +static int dup_normal = 1; + +static void *dup_discarded + __attribute__((section(DISCARDED_SEC), used)) = &dup_normal; + +int FUNC_NAME(void) +{ + return dup_normal + (dup_discarded != (void *)0); +} diff --git a/tools/objtool/tests/generic/test-symid-discarded.sh b/tools/objtool/tests/generic/test-symid-discarded.sh new file mode 100755 index 000000000000..388a24984ec0 --- /dev/null +++ b/tools/objtool/tests/generic/test-symid-discarded.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# .klp.symid must not reference symbols in sections the vmlinux link discards. +# Each such section has been its own bug, found only when someone built a +# config where a duplicate happened to land there, so cover the whole list +# rather than whichever one was reported last. + +. "$(dirname "$0")/../lib.sh" + +setup + +# Allocated sections which vmlinux.lds.h discards unconditionally. A symid +# referencing one of these fails the vmlinux link outright: +# +# `__exitcall_hid_exit' referenced in section `.klp.symid' of vmlinux.o: +# defined in discarded section `.exitcall.exit' of vmlinux.o +for sec in .exitcall.exit .no_trim_symbol; do + build_one symid_discarded.c a.o \ + -DFUNC_NAME=use_a -DDISCARDED_SEC="\"$sec\"" + build_one symid_discarded.c b.o \ + -DFUNC_NAME=use_b -DDISCARDED_SEC="\"$sec\"" + + # --klp-symids only runs on a file named vmlinux.o + rm -f "$workdir/vmlinux.o" + partial_link "$workdir/vmlinux.o" "$workdir/a.o" "$workdir/b.o" || + probe_skip "partial link unavailable" + + "$OBJTOOL" --klp-symids --link "$workdir/vmlinux.o" || + fail "objtool --klp-symids failed" + + symids="$(in_relocs vmlinux.o | + awk '/rela.klp.symid/,/^$/')" + + # Without this the test would also pass if symid generation stopped + # entirely. + echo "$symids" | grep -q 'dup_normal' || + fail "$sec: no symid for the duplicate in a live section" + + echo "$symids" | grep -q 'dup_discarded' && + fail "symid emitted for a symbol in discarded section $sec" +done + +pass "no symids for symbols in discarded sections" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 23/58] objtool/klp: Add test for rejecting references to init code/data 2026-09-11 18:42 [PATCH 00/58] Unit test framework for klp-build toolchain Song Liu ` (21 preceding siblings ...) 2026-09-11 18:42 ` [PATCH 22/58] objtool/klp: Add test for symids in discarded sections Song Liu @ 2026-09-11 18:42 ` Song Liu 2026-09-11 19:18 ` sashiko-bot 2026-09-11 18:42 ` [PATCH 24/58] objtool/klp: Add test for correlation across ThinLTO name mangling Song Liu ` (3 subsequent siblings) 26 siblings, 1 reply; 77+ messages in thread From: Song Liu @ 2026-09-11 18:42 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu From: Puranjay Mohan <puranjay@kernel.org> Init code and data are freed once boot finishes, so a klp relocation against them can never resolve. Signed-off-by: Puranjay Mohan <puranjay@kernel.org> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../tests/generic/fixtures/init_reference.c | 16 ++++++++++++++++ .../objtool/tests/generic/test-init-reference.sh | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/init_reference.c create mode 100755 tools/objtool/tests/generic/test-init-reference.sh diff --git a/tools/objtool/tests/generic/fixtures/init_reference.c b/tools/objtool/tests/generic/fixtures/init_reference.c new file mode 100644 index 000000000000..51b2a57dde14 --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/init_reference.c @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Patched function referencing data in an .init section. */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +static int init_only __attribute__((section(".init.data"), used)) = 5; + +int target(int x) +{ +#ifdef PATCHED + return x + init_only + 1; +#else + return x + init_only; +#endif +} diff --git a/tools/objtool/tests/generic/test-init-reference.sh b/tools/objtool/tests/generic/test-init-reference.sh new file mode 100755 index 000000000000..8bf4b98bf458 --- /dev/null +++ b/tools/objtool/tests/generic/test-init-reference.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Init code and data are freed after boot, so a klp relocation against them can +# never resolve. Such a patch must be rejected. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair init_reference.c +run_diff 255 + +diff_log | grep -q "can't patch or reference init code/data" || + fail "expected rejection, got: $(diff_log | tail -1)" + +pass "reference to init data rejected" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* Re: [PATCH 23/58] objtool/klp: Add test for rejecting references to init code/data 2026-09-11 18:42 ` [PATCH 23/58] objtool/klp: Add test for rejecting references to init code/data Song Liu @ 2026-09-11 19:18 ` sashiko-bot 0 siblings, 0 replies; 77+ messages in thread From: sashiko-bot @ 2026-09-11 19:18 UTC (permalink / raw) To: Song Liu, live-patching Cc: joe.lawrence, kernel-team, puranjay, mbenes, jikos, peterz, jpoimboe, pmladek Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: New issues: - [Medium] The test fixture `init_reference.c` defines `init_only` as a `static` variable that is never written to, which allows the compiler to fold its read into a constant. This prevents the expected relocation from being emitted, breaking the test. Pre-existing issues: - [High] `objtool klp` fails to reject references to exported init symbols because `klp_find_sympos()` (which enforces the init check) is completely bypassed for exports. -- commit 89481f125a4328fb499128f2365603f31f38782d Author: Puranjay Mohan <puranjay@kernel.org> objtool/klp: Add test for rejecting references to init code/data This commit introduces a new test fixture and script to ensure objtool's klp mode correctly rejects livepatches that reference init code or data. > diff --git a/tools/objtool/tests/generic/fixtures/init_reference.c b/tools/objtool/tests/generic/fixtures/init_reference.c > new file mode 100644 > index 0000000000000..51b2a57dde14f > --- /dev/null > +++ b/tools/objtool/tests/generic/fixtures/init_reference.c > @@ -0,0 +1,16 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* Patched function referencing data in an .init section. */ > + > +static const char __modinfo[] > + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; > + > +static int init_only __attribute__((section(".init.data"), used)) = 5; > + > +int target(int x) > +{ > +#ifdef PATCHED > + return x + init_only + 1; [Severity: Medium] Since init_only is declared static and is never written to, won't the compiler optimize its reads in target() via constant folding? If the compiler replaces the read with the literal 5, no relocation to the .init.data section will be emitted in the resulting object file. Does this missing relocation prevent objtool klp from detecting the reference, causing the test to silently fail its intended validation? > +#else > + return x + init_only; > +#endif > +} > diff --git a/tools/objtool/tests/generic/test-init-reference.sh b/tools/objtool/tests/generic/test-init-reference.sh > new file mode 100755 > index 0000000000000..8bf4b98bf4585 > --- /dev/null > +++ b/tools/objtool/tests/generic/test-init-reference.sh [ ... ] > +setup > +build_pair init_reference.c > +run_diff 255 > + > +diff_log | grep -q "can't patch or reference init code/data" || [Severity: High] This is a pre-existing issue, but while reviewing this test, I noticed that the safety check it validates can be completely bypassed for exported symbols. In tools/objtool/klp-diff.c:clone_reloc_klp(), the branch handling exports bypasses klp_find_sympos(): if (export) { sym_modname = export->mod; sym_orig_name = export->sym; sympos = 0; } else { ... sympos = klp_find_sympos(e->orig, patched_sym->twin); } Because is_init_sym() is only checked inside klp_find_sympos(), objtool klp will silently accept livepatches referencing exported .init code or data. Does this gap in validation allow a livepatch to execute freed memory or cause a use-after-free at runtime if it references an exported init symbol? > + fail "expected rejection, got: $(diff_log | tail -1)" > + > +pass "reference to init data rejected" -- Sashiko AI review · https://sashiko.dev/#/patchset/20260911185031.1534046-1-song@kernel.org?part=23 ^ permalink raw reply [flat|nested] 77+ messages in thread
* [PATCH 24/58] objtool/klp: Add test for correlation across ThinLTO name mangling 2026-09-11 18:42 [PATCH 00/58] Unit test framework for klp-build toolchain Song Liu ` (22 preceding siblings ...) 2026-09-11 18:42 ` [PATCH 23/58] objtool/klp: Add test for rejecting references to init code/data Song Liu @ 2026-09-11 18:42 ` Song Liu 2026-09-11 18:42 ` [PATCH 25/58] objtool/klp: Add test for objects without .modinfo Song Liu ` (2 subsequent siblings) 26 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:42 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu From: Puranjay Mohan <puranjay@kernel.org> ThinLTO promotes the file-local symbols an imported function touches, renaming them name.llvm.<hash>. The hash is content derived, so it changes whenever the module does: original: counter.llvm.13663304415433785070 patched: counter.llvm.10543383937958011340 Correlating the two objects therefore requires demangling the suffix; matching raw names would see two unrelated symbols and treat the variable as new. The resulting klp relocation also has to name the original symbol, since that is the one in the running kernel's kallsyms. Naming the patched build's symbol produces a relocation which can never be resolved. ThinLTO is a clang feature, so the test declares itself clang-only. It also needs an lld from the same LLVM release as $CC; a mismatched pair fails with "Invalid summary version", which reads like a broken test rather than a broken environment, so probe for a working lld and skip if there is none. Signed-off-by: Puranjay Mohan <puranjay@kernel.org> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../tests/generic/fixtures/thinlto_local.c | 39 +++++++++++++++ .../tests/generic/test-thinlto-local.sh | 48 +++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/thinlto_local.c create mode 100755 tools/objtool/tests/generic/test-thinlto-local.sh diff --git a/tools/objtool/tests/generic/fixtures/thinlto_local.c b/tools/objtool/tests/generic/fixtures/thinlto_local.c new file mode 100644 index 000000000000..fe9f9e6bf44f --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/thinlto_local.c @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Two translation units (TU_B selects the second) linked with ThinLTO. + * Importing bump() promotes the file-local counter, renaming it + * counter.llvm.<hash>. The hash is content derived, so it differs between the + * original and patched builds. + */ + +#ifdef TU_B + +extern int bump(void); + +int other_entry(void) +{ + return bump() + bump(); +} + +#else + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +static int counter; + +int bump(void) +{ + return ++counter; +} + +int target(void) +{ +#ifdef PATCHED + return counter + 1; +#else + return counter; +#endif +} + +#endif diff --git a/tools/objtool/tests/generic/test-thinlto-local.sh b/tools/objtool/tests/generic/test-thinlto-local.sh new file mode 100755 index 000000000000..a266263676ed --- /dev/null +++ b/tools/objtool/tests/generic/test-thinlto-local.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Correlating ThinLTO-promoted locals requires demangling the .llvm.<hash> +# suffix, and the resulting klp relocation must name the original symbol: that +# is the one in the running kernel's kallsyms. + +. "$(dirname "$0")/../lib.sh" + +setup +clang_only "ThinLTO requires clang" + +find_thinlto_toolchain || + probe_skip "no matching clang/lld pair for a ThinLTO link; set THIN_LD to one" + +build_thinlto() # $1 output object, $2 extra flags +{ + $THIN_CC -flto=thin -O2 -ffunction-sections -fdata-sections $2 \ + -c "$FIXTURES_DIR/thinlto_local.c" -o "$workdir/tu_a.o" 2>/dev/null || return 1 + $THIN_CC -flto=thin -O2 -ffunction-sections -fdata-sections $2 -DTU_B \ + -c "$FIXTURES_DIR/thinlto_local.c" -o "$workdir/tu_b.o" 2>/dev/null || return 1 + "$THIN_LD" -r "$workdir/tu_a.o" "$workdir/tu_b.o" -o "$1" 2>/dev/null || return 1 +} + +build_thinlto "$workdir/orig.o" "" || + probe_skip "ThinLTO build failed ($THIN_CC, $THIN_LD)" +build_thinlto "$workdir/patched.o" -DPATCHED || + probe_skip "ThinLTO build failed ($THIN_CC, $THIN_LD)" + +orig_sym="$(in_symbols orig.o | grep -o 'counter\.llvm\.[0-9]*' | head -1)" +new_sym="$( in_symbols patched.o | grep -o 'counter\.llvm\.[0-9]*' | head -1)" + +[ -n "$orig_sym" ] && [ -n "$new_sym" ] || + probe_skip "$THIN_CC did not promote the local symbol" + +# Equal hashes would make plain name matching work, testing nothing. +[ "$orig_sym" != "$new_sym" ] || + probe_skip "$THIN_CC gave the same ThinLTO hash for both builds" + +run_diff +assert_patched target + +out_symbols | grep -q "\.klp\.sym\.vmlinux\.$orig_sym," || + fail "expected a klp relocation naming $orig_sym" +out_symbols | grep -q "\.klp\.sym\.vmlinux\.$new_sym," && + fail "klp relocation names $new_sym, which the running kernel does not have" + +pass "ThinLTO-mangled local correlated across differing hashes ($THIN_CC, $THIN_LD)" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 25/58] objtool/klp: Add test for objects without .modinfo 2026-09-11 18:42 [PATCH 00/58] Unit test framework for klp-build toolchain Song Liu ` (23 preceding siblings ...) 2026-09-11 18:42 ` [PATCH 24/58] objtool/klp: Add test for correlation across ThinLTO name mangling Song Liu @ 2026-09-11 18:42 ` Song Liu 2026-09-11 18:49 ` [PATCH 26/58] objtool/klp: Add test for unchecksummed input Song Liu 2026-09-13 1:53 ` [PATCH 00/58] Unit test framework for klp-build toolchain Josh Poimboeuf 26 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:42 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu From: Puranjay Mohan <puranjay@kernel.org> The module name recorded in .modinfo ends up in the livepatch's klp_object, and also decides whether a static branch key counts as belonging to vmlinux or to a module. An object without one cannot be diffed. Signed-off-by: Puranjay Mohan <puranjay@kernel.org> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../objtool/tests/generic/fixtures/no_modinfo.c | 11 +++++++++++ .../tests/generic/test-missing-modinfo.sh | 16 ++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/no_modinfo.c create mode 100755 tools/objtool/tests/generic/test-missing-modinfo.sh diff --git a/tools/objtool/tests/generic/fixtures/no_modinfo.c b/tools/objtool/tests/generic/fixtures/no_modinfo.c new file mode 100644 index 000000000000..e46374702b1a --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/no_modinfo.c @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Deliberately has no .modinfo section. */ + +int target(int x) +{ +#ifdef PATCHED + return x + 2; +#else + return x + 1; +#endif +} diff --git a/tools/objtool/tests/generic/test-missing-modinfo.sh b/tools/objtool/tests/generic/test-missing-modinfo.sh new file mode 100755 index 000000000000..6f242f7eb756 --- /dev/null +++ b/tools/objtool/tests/generic/test-missing-modinfo.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# The module name in .modinfo ends up in the livepatch's klp_object, so an +# object without one cannot be diffed. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair no_modinfo.c +run_diff 255 + +diff_log | grep -q 'modinfo' || + fail "expected a complaint about .modinfo, got: $(diff_log | tail -1)" + +pass "object without .modinfo rejected" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 26/58] objtool/klp: Add test for unchecksummed input 2026-09-11 18:42 [PATCH 00/58] Unit test framework for klp-build toolchain Song Liu ` (24 preceding siblings ...) 2026-09-11 18:42 ` [PATCH 25/58] objtool/klp: Add test for objects without .modinfo Song Liu @ 2026-09-11 18:49 ` Song Liu 2026-09-11 18:50 ` [PATCH 27/58] objtool/klp: Add klp diff and post-link regression tests Song Liu ` (31 more replies) 2026-09-13 1:53 ` [PATCH 00/58] Unit test framework for klp-build toolchain Josh Poimboeuf 26 siblings, 32 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:49 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu From: Puranjay Mohan <puranjay@kernel.org> klp diff decides what changed by comparing the per-function checksums klp checksum records in .discard.sym_checksum. Handed an object which was never checksummed it has to say so; silently concluding that nothing changed would be the worst available answer. Signed-off-by: Puranjay Mohan <puranjay@kernel.org> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../tests/generic/test-missing-checksum.sh | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100755 tools/objtool/tests/generic/test-missing-checksum.sh diff --git a/tools/objtool/tests/generic/test-missing-checksum.sh b/tools/objtool/tests/generic/test-missing-checksum.sh new file mode 100755 index 000000000000..89a05b1869ed --- /dev/null +++ b/tools/objtool/tests/generic/test-missing-checksum.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Without .discard.sym_checksum there is nothing to compare; concluding that +# nothing changed would be worse than failing. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair basic.c + +( cd "$workdir" && "$OBJTOOL" klp diff orig.o patched.o out.o ) \ + > "$workdir/diff.log" 2>&1 && fail "klp diff accepted an unchecksummed object" + +grep -q 'sym_checksum' "$workdir/diff.log" || + fail "expected a complaint about the checksum section, got: $(tail -1 "$workdir/diff.log")" + +pass "unchecksummed input rejected" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 27/58] objtool/klp: Add klp diff and post-link regression tests 2026-09-11 18:49 ` [PATCH 26/58] objtool/klp: Add test for unchecksummed input Song Liu @ 2026-09-11 18:50 ` Song Liu 2026-09-11 18:50 ` [PATCH 28/58] objtool/klp: Add test for klp reloc section naming in module objects Song Liu ` (30 subsequent siblings) 31 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:50 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu Five tests covering behaviour the harness could reach but nothing exercised. Each was verified to fail against the code it guards, by reverting the fix or sabotaging the exact line; where a first attempt passed against broken code, the fixture was wrong and was rebuilt. post-link first coverage of the subcommand at all local-vs-export local symbols must not match exports symvers-parse-error Module.symvers parse error line numbers checksum-debug the --debug-checksum format klp-build reads function-removal the "no correlation" path local-vs-export tests the behavior of commit 86a697572c62 ("objtool/klp: Don't match local symbols against exports"), and symvers-parse-error that of commit 51c1de134863 ("objtool/klp: Fix line numbers in Module.symvers parse errors"). Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../tests/generic/fixtures/function_removal.c | 25 ++++++++++ .../tests/generic/test-checksum-debug.sh | 49 +++++++++++++++++++ .../tests/generic/test-function-removal.sh | 34 +++++++++++++ .../tests/generic/test-local-vs-export.sh | 32 ++++++++++++ tools/objtool/tests/generic/test-post-link.sh | 39 +++++++++++++++ .../tests/generic/test-symvers-parse-error.sh | 23 +++++++++ 6 files changed, 202 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/function_removal.c create mode 100755 tools/objtool/tests/generic/test-checksum-debug.sh create mode 100755 tools/objtool/tests/generic/test-function-removal.sh create mode 100755 tools/objtool/tests/generic/test-local-vs-export.sh create mode 100755 tools/objtool/tests/generic/test-post-link.sh create mode 100755 tools/objtool/tests/generic/test-symvers-parse-error.sh diff --git a/tools/objtool/tests/generic/fixtures/function_removal.c b/tools/objtool/tests/generic/fixtures/function_removal.c new file mode 100644 index 000000000000..d65ff604c2c5 --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/function_removal.c @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A function the patch deletes, along with its only caller's use of it. The + * original has a symbol which the patched object simply does not, so there is + * nothing to correlate it against. + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +#ifndef PATCHED +int going_away(int x) +{ + return x + 7; +} +#endif + +int caller(int x) +{ +#ifdef PATCHED + return x + 1; +#else + return going_away(x); +#endif +} diff --git a/tools/objtool/tests/generic/test-checksum-debug.sh b/tools/objtool/tests/generic/test-checksum-debug.sh new file mode 100755 index 000000000000..78856b191636 --- /dev/null +++ b/tools/objtool/tests/generic/test-checksum-debug.sh @@ -0,0 +1,49 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# "klp checksum --debug-checksum" prints a per-instruction checksum stream, +# and klp-build -f (--show-first-changed) parses it to report where a function +# first differs between the original and patched builds. +# +# It is a debugging aid, so nothing fails when it breaks: klp-build greps the +# stream, and an unmatched grep just yields no output, which reads as "no +# instruction changed". That is exactly how the format drifted out from under +# it once already. Pin the shape klp-build depends on: +# +# DEBUG: <object>: checksum: <func>(): <sym>+0x<offset> <16 hex digits> +# +# and that --dry-run leaves the object alone, since klp-build runs this against +# objects it is going to checksum again for real. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair basic.c + +before="$(md5sum < "$workdir/orig.o")" + +"$OBJTOOL" klp checksum --dry-run --debug-checksum=changed \ + "$workdir/orig.o" > "$workdir/debug.log" 2>&1 || + fail "klp checksum --debug-checksum failed" + +# --dry-run has to mean it: klp-build checksums these objects again afterwards, +# and "already has .discard.sym_checksum, skipping" would lose the real run. +[ "$(md5sum < "$workdir/orig.o")" = "$before" ] || + fail "--dry-run modified the object" +has_input_section orig.o .discard.sym_checksum && + fail "--dry-run created .discard.sym_checksum" + +grep -qE '^DEBUG: .*: checksum: changed\(\): [^ ]+\+0x[0-9a-f]+ [0-9a-f]{16}$' \ + "$workdir/debug.log" || + fail "unexpected --debug-checksum format: $(head -1 "$workdir/debug.log")" + +# This is the pattern klp-build greps with. Keep it working verbatim. +grep -qE "^DEBUG: .*checksum: changed\(\): " "$workdir/debug.log" || + fail "klp-build's --show-first-changed pattern no longer matches" + +# Only the requested function, or klp-build attributes instructions to the +# wrong one. +grep -qE 'checksum: untouched\(\)' "$workdir/debug.log" && + fail "--debug-checksum=changed also dumped untouched()" + +pass "--debug-checksum format is the one klp-build -f parses" diff --git a/tools/objtool/tests/generic/test-function-removal.sh b/tools/objtool/tests/generic/test-function-removal.sh new file mode 100755 index 000000000000..df76e81e3936 --- /dev/null +++ b/tools/objtool/tests/generic/test-function-removal.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# A patch which deletes a function leaves a symbol in the original with no +# counterpart in the patched object. klp diff cannot correlate it, and must +# say so and carry on: livepatching cannot remove code from a running kernel, +# so what matters is that the surviving caller is patched and the deleted +# function is not dragged into the patch module. +# +# Cloning it would be worse than useless -- dead code in the patch, plus +# whatever it references, resolved against a kernel where it may not exist. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair function_removal.c + +# One-sided by construction: present in the original, gone from the patched. +has_input_symbol orig.o going_away || + fail "fixture has no going_away in the original" +has_input_symbol patched.o going_away && + fail "fixture still has going_away in the patched object" + +run_diff + +assert_diff_log 'no correlation: going_away' + +# The caller changed, so it is patched ... +assert_patched caller +# ... and the deleted function comes along in no form at all. +assert_not_patched going_away +assert_no_symbol going_away + +pass "deleted function reported as uncorrelated and left out of the patch" diff --git a/tools/objtool/tests/generic/test-local-vs-export.sh b/tools/objtool/tests/generic/test-local-vs-export.sh new file mode 100755 index 000000000000..5b91101dadd6 --- /dev/null +++ b/tools/objtool/tests/generic/test-local-vs-export.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# find_export() matched on symbol name alone, so a static function or variable +# sharing a name with an export was mistaken for a reference to that export. +# For a vmlinux export that means no klp relocation at all: the normal +# relocation left behind is resolved by the module loader to the vmlinux +# symbol, and the patched code quietly reads and writes the wrong object. +# +# Exports are always global, so a local symbol is never one. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair static_local.c + +# The static the fixture uses. Compilers mangle statics variously -- gcc says +# counter.0, clang says target.counter -- so find what this one produced rather +# than assuming a shape. +local_sym="$(in_symbols orig.o | + awk '$4 == "OBJECT" && $5 == "LOCAL" && $8 ~ /counter/ { print $8; exit }')" +[ -n "$local_sym" ] || + fail "fixture produced no local 'counter' symbol" + +# Contrive the collision: something else exports that same name. +export_syms "$local_sym" counter +run_diff + +# Still treated as the local it is, not as the export. +assert_klp_sym "$local_sym" vmlinux + +pass "local symbol not mistaken for an export of the same name" diff --git a/tools/objtool/tests/generic/test-post-link.sh b/tools/objtool/tests/generic/test-post-link.sh new file mode 100755 index 000000000000..39c6cab9db1e --- /dev/null +++ b/tools/objtool/tests/generic/test-post-link.sh @@ -0,0 +1,39 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# klp post-link converts the intermediate __klp_relocs.* sections into the +# .klp.rela.* form the kernel applies at patch load. Getting this wrong is +# invisible at build time: the module links and loads, and the relocations are +# simply never applied. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair static_local.c + +# An unexported symbol is what produces a klp relocation in the first place. +assert_input_symbol counter +run_diff +assert_section __klp_relocs.vmlinux + +# Nothing has converted them yet. +assert_no_section ".klp.rela.vmlinux..text.target" + +# The original relocation is neutralised by pointing it at a tombstone, which +# is what stops the module loader resolving it behind livepatch's back. +# +# Compilers mangle a static local differently -- gcc says counter.0, clang +# target.counter -- so find what this one produced rather than assuming. +local_sym="$(in_symbols orig.o | + awk '$4 == "OBJECT" && $5 == "LOCAL" && $8 ~ /counter/ { print $8; exit }')" +[ -n "$local_sym" ] || fail "fixture produced no local 'counter' symbol" +assert_tombstone "$local_sym" + +run_post_link + +# One .klp.rela section per base section, carrying SHF_RELA_LIVEPATCH, against +# a symbol in SHN_LIVEPATCH for the kernel to resolve. +assert_klp_rela vmlinux .text.target +assert_livepatch_sym counter + +pass "klp relocations converted to .klp.rela with SHN_LIVEPATCH symbols" diff --git a/tools/objtool/tests/generic/test-symvers-parse-error.sh b/tools/objtool/tests/generic/test-symvers-parse-error.sh new file mode 100755 index 000000000000..d597f28e5617 --- /dev/null +++ b/tools/objtool/tests/generic/test-symvers-parse-error.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# A malformed Module.symvers has to be reported against the line it is on. +# Module.symvers has tens of thousands of lines and is generated, so a wrong +# line number sends whoever has to fix it to the wrong place, and "line 1" is +# wrong in a way that looks plausible. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair basic.c + +# Three well-formed lines, then one with no tabs at all. +export_syms a b c +echo 'this line has no fields' >> "$workdir/Module.symvers" + +run_diff 255 + +assert_diff_log 'malformed Module.symvers' +assert_diff_log 'at line 4' + +pass "malformed Module.symvers reported against the offending line" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 28/58] objtool/klp: Add test for klp reloc section naming in module objects 2026-09-11 18:49 ` [PATCH 26/58] objtool/klp: Add test for unchecksummed input Song Liu 2026-09-11 18:50 ` [PATCH 27/58] objtool/klp: Add klp diff and post-link regression tests Song Liu @ 2026-09-11 18:50 ` Song Liu 2026-09-11 18:50 ` [PATCH 29/58] objtool/klp: Add test for vmlinux relocs in a patched module Song Liu ` (29 subsequent siblings) 31 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:50 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu klp diff names the intermediate __klp_relocs section after the object the relocation belongs to, and post-link turns that into .klp.rela.<object>.<sec>. Name it after the wrong object and the kernel applies the relocation when the wrong module loads, or never. The fixture is the first here to honour MODNAME: most hardcode name=vmlinux, so passing -DMODNAME to them silently does nothing and the test quietly becomes a vmlinux test. This tests the behavior of commit 07f14d6af9d7 ("objtool/klp: Fix cross-module klp relocation section naming"). Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence <joe.lawrence@redhat.com> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../tests/generic/fixtures/cross_module.c | 25 +++++++++++++++ .../tests/generic/test-module-object.sh | 31 +++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/cross_module.c create mode 100755 tools/objtool/tests/generic/test-module-object.sh diff --git a/tools/objtool/tests/generic/fixtures/cross_module.c b/tools/objtool/tests/generic/fixtures/cross_module.c new file mode 100644 index 000000000000..c170bde0666f --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/cross_module.c @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A function which calls out to another object. MODNAME selects which object + * this one is, so a test can make the caller a module and the callee's owner + * something else. + */ + +#ifndef MODNAME +#define MODNAME "vmlinux" +#endif + +/* klp diff takes the object's module name from .modinfo */ +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=" MODNAME; + +extern int other_mod_func(int x); + +int target(int x) +{ +#ifdef PATCHED + return other_mod_func(x) + 2; +#else + return other_mod_func(x) + 1; +#endif +} diff --git a/tools/objtool/tests/generic/test-module-object.sh b/tools/objtool/tests/generic/test-module-object.sh new file mode 100755 index 000000000000..95c8402a523a --- /dev/null +++ b/tools/objtool/tests/generic/test-module-object.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# A klp relocation section is named for the object being patched, not for the +# object which happens to own the symbol being referenced. Deriving it from +# the symbol means a cross-module reference lands in a section for an object +# the patch may not even touch, so the relocation is never applied and the call +# goes somewhere arbitrary. + +. "$(dirname "$0")/../lib.sh" + +setup +build_module_pair cross_module.c klp_testmod + +# The fixture has to have built as a module for any of this to mean anything. +in_sections orig.o | grep -q '\.modinfo' || + fail "fixture has no .modinfo" + +# other_mod_func belongs to a different module than the one being patched. +add_exports other_mod other_mod_func +run_diff + +# Named for the patched object ... +assert_section __klp_relocs.klp_testmod +# ... not for the object owning the symbol. +assert_no_section __klp_relocs.other_mod + +run_post_link +assert_klp_rela klp_testmod .text.target + +pass "klp relocation section named for the patched object" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 29/58] objtool/klp: Add test for vmlinux relocs in a patched module 2026-09-11 18:49 ` [PATCH 26/58] objtool/klp: Add test for unchecksummed input Song Liu 2026-09-11 18:50 ` [PATCH 27/58] objtool/klp: Add klp diff and post-link regression tests Song Liu 2026-09-11 18:50 ` [PATCH 28/58] objtool/klp: Add test for klp reloc section naming in module objects Song Liu @ 2026-09-11 18:50 ` Song Liu 2026-09-11 18:50 ` [PATCH 30/58] objtool/klp: Add test for Module.symvers path normalization Song Liu ` (28 subsequent siblings) 31 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:50 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu, Dylan Hatch The kernel refuses a module-targeted klp relocation which names a vmlinux symbol. An EXPORT_SYMBOL_FOR_MODULES() symbol needs a klp relocation, so patching a module function that references one only loads if klp diff files that relocation under vmlinux rather than under the patched module. This fails at load, not at build: klp-build produces a module and static checks of it find nothing wrong. Hence the assertion on which object the relocation is filed against. Reported by Dylan Hatch, whose mod-ns-lp branch carries the kernel-side half of this case. Signed-off-by: Dylan Hatch <dylanbhatch@google.com> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../generic/test-module-vmlinux-reloc.sh | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100755 tools/objtool/tests/generic/test-module-vmlinux-reloc.sh diff --git a/tools/objtool/tests/generic/test-module-vmlinux-reloc.sh b/tools/objtool/tests/generic/test-module-vmlinux-reloc.sh new file mode 100755 index 000000000000..635a75f6b8ba --- /dev/null +++ b/tools/objtool/tests/generic/test-module-vmlinux-reloc.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Patching a module, where the patched code references a vmlinux symbol which +# needs a klp relocation. +# +# The kernel does not allow a module-targeted klp relocation to reference a +# vmlinux symbol, and a symbol exported with EXPORT_SYMBOL_FOR_MODULES gets a +# klp relocation. Put together, filing that relocation under the patched +# module produces a patch the kernel refuses to apply to its target. +# +# So it goes under vmlinux instead, and is applied when the patch module loads +# rather than when the patched module does. That is the opposite of the rule +# for a reference to a module's symbol, which test-module-object covers; this +# is the other branch of the same decision. + +. "$(dirname "$0")/../lib.sh" + +setup +# The object being patched is a module ... +build_module_pair cross_module.c klp_testmod + +# ... and the symbol it references belongs to vmlinux, exported in a way that +# still requires a klp relocation. +export_syms +add_exports_ns vmlinux module:kvm other_mod_func +run_diff + +# Filed against vmlinux, applied when the patch loads. +assert_section __klp_relocs.vmlinux +assert_klp_sym other_mod_func vmlinux + +# Not against the patched module: that is the relocation the kernel rejects. +assert_no_section __klp_relocs.klp_testmod + +run_post_link +assert_klp_rela vmlinux .text.target +assert_no_section ".klp.rela.klp_testmod..text.target" + +pass "klp relocation to a vmlinux symbol filed under vmlinux, not the patched module" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 30/58] objtool/klp: Add test for Module.symvers path normalization 2026-09-11 18:49 ` [PATCH 26/58] objtool/klp: Add test for unchecksummed input Song Liu ` (2 preceding siblings ...) 2026-09-11 18:50 ` [PATCH 29/58] objtool/klp: Add test for vmlinux relocs in a patched module Song Liu @ 2026-09-11 18:50 ` Song Liu 2026-09-11 18:50 ` [PATCH 31/58] objtool/klp: Add test for the contents of the klp_funcs list Song Liu ` (27 subsequent siblings) 31 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:50 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu Module.symvers names an export's owner by build path, and the kernel knows modules by their runtime name. Without normalizing one to the other, an export owned by a module is not recognised as owned by anything, and a reference that should become a klp relocation stays an ordinary one. This tests the behavior of commit 8668bf91e050 ("objtool/klp: Normalize Module.symvers paths to module names"). Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence <joe.lawrence@redhat.com> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../tests/generic/test-modname-normalize.sh | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100755 tools/objtool/tests/generic/test-modname-normalize.sh diff --git a/tools/objtool/tests/generic/test-modname-normalize.sh b/tools/objtool/tests/generic/test-modname-normalize.sh new file mode 100755 index 000000000000..a8059bdbd667 --- /dev/null +++ b/tools/objtool/tests/generic/test-modname-normalize.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Module.symvers records the build-tree path of the object that exports a +# symbol, not the name the module has at runtime: "arch/x86/kvm/kvm-intel", +# where the kernel knows the module as "kvm_intel". +# +# The klp symbol name embeds the owning object, and livepatch matches it +# against loaded modules by name. Left unnormalized it names a module that +# does not exist, and the relocation is never resolved -- at load time, with no +# build-time complaint. + +. "$(dirname "$0")/../lib.sh" + +setup +build_module_pair cross_module.c klp_testmod + +# A path with directory components, a dash, and no extension. +export_syms +add_exports "arch/x86/kvm/kvm-intel" other_mod_func +run_diff + +# Directories stripped, dash to underscore. +assert_klp_sym other_mod_func kvm_intel + +pass "Module.symvers paths normalized to runtime module names" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 31/58] objtool/klp: Add test for the contents of the klp_funcs list 2026-09-11 18:49 ` [PATCH 26/58] objtool/klp: Add test for unchecksummed input Song Liu ` (3 preceding siblings ...) 2026-09-11 18:50 ` [PATCH 30/58] objtool/klp: Add test for Module.symvers path normalization Song Liu @ 2026-09-11 18:50 ` Song Liu 2026-09-11 18:50 ` [PATCH 32/58] objtool/klp: Add test for EXPORT_SYMBOL_FOR_MODULES references Song Liu ` (26 subsequent siblings) 31 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:50 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu The patch list is what livepatch acts on, and asserting only that it exists does not say it is right. A function that should have been patched and is missing leaves the bug in place; one that should not be there patches code nobody changed. The fixture changes two of three functions and asserts on all three: the two by name, and the third by its absence. It checks the strings in .rodata.klp.str1.1 as well as the relocations, since the names the kernel matches on are real strings. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence <joe.lawrence@redhat.com> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../tests/generic/fixtures/klp_funcs.c | 31 +++++++++++++ .../tests/generic/test-klp-funcs-content.sh | 44 +++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/klp_funcs.c create mode 100755 tools/objtool/tests/generic/test-klp-funcs-content.sh diff --git a/tools/objtool/tests/generic/fixtures/klp_funcs.c b/tools/objtool/tests/generic/fixtures/klp_funcs.c new file mode 100644 index 000000000000..3f0d3e206cef --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/klp_funcs.c @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Two changed functions and one untouched, so the patch's function list has a + * length worth checking and something that must not appear in it. + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +int first(int x) +{ +#ifdef PATCHED + return x + 11; +#else + return x + 1; +#endif +} + +int second(int x) +{ +#ifdef PATCHED + return x + 22; +#else + return x + 2; +#endif +} + +int third(int x) +{ + return x + 3; +} diff --git a/tools/objtool/tests/generic/test-klp-funcs-content.sh b/tools/objtool/tests/generic/test-klp-funcs-content.sh new file mode 100755 index 000000000000..c4ec7098d948 --- /dev/null +++ b/tools/objtool/tests/generic/test-klp-funcs-content.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# .init.klp_funcs is the list the kernel walks to decide what to patch, and +# .init.klp_objects points at it. Existing tests assert only that the sections +# exist, which they do whether the list names the right functions, the wrong +# ones, or none at all -- and a patch module with an empty function list loads +# perfectly happily and patches nothing. +# +# Each entry pairs a name string in .rodata.klp.str1.1 with a relocation to the +# new function, so both halves are checkable. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair klp_funcs.c +run_diff + +assert_section .init.klp_funcs +assert_section .init.klp_objects + +# Two functions changed, so two entries, each contributing a name relocation +# and a function relocation. +assert_reloc_count .init.klp_funcs 4 + +# The functions that changed are named ... +assert_reloc_sym .init.klp_funcs first +assert_reloc_sym .init.klp_funcs second +# ... and the one that did not is absent, from the list and from the patch. +assert_no_reloc_sym .init.klp_funcs third +assert_not_patched third + +# The names the kernel matches on are real strings, not just relocations. +out_strings .rodata.klp.str1.1 | + grep -q '\bfirst\b' || + fail "no 'first' string in .rodata.klp.str1.1" +out_strings .rodata.klp.str1.1 | + grep -q '\bsecond\b' || + fail "no 'second' string in .rodata.klp.str1.1" + +# The object list has to reach the function list, or nothing is walked. +assert_reloc_sym .init.klp_objects .init.klp_funcs + +pass "klp_funcs lists exactly the changed functions, by name and relocation" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 32/58] objtool/klp: Add test for EXPORT_SYMBOL_FOR_MODULES references 2026-09-11 18:49 ` [PATCH 26/58] objtool/klp: Add test for unchecksummed input Song Liu ` (4 preceding siblings ...) 2026-09-11 18:50 ` [PATCH 31/58] objtool/klp: Add test for the contents of the klp_funcs list Song Liu @ 2026-09-11 18:50 ` Song Liu 2026-09-11 18:50 ` [PATCH 33/58] objtool/klp: Add test for new references to exported symbols Song Liu ` (25 subsequent siblings) 31 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:50 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu A symbol exported with EXPORT_SYMBOL_FOR_MODULES() is reachable only by the modules named in its namespace, and a livepatch module is never one of them. So a reference to it cannot be an ordinary relocation resolved by the module loader; it has to be a klp relocation applied at patch time. Getting this wrong is silent. The module links, loads, and reads the wrong thing, or fails to load for a reason that does not name the cause. This tests the behavior of commit 4cd3cfb8b54f ("objtool/klp: Fix relocations for EXPORT_SYMBOL_FOR_MODULES() symbols") and commit d8a2860b4a36 ("objtool/klp: Fix vmlinux klp relocations for EXPORT_SYMBOL_FOR_MODULES()"). Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence <joe.lawrence@redhat.com> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../generic/test-export-symbol-for-modules.sh | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100755 tools/objtool/tests/generic/test-export-symbol-for-modules.sh diff --git a/tools/objtool/tests/generic/test-export-symbol-for-modules.sh b/tools/objtool/tests/generic/test-export-symbol-for-modules.sh new file mode 100755 index 000000000000..7e7bdde6a7ac --- /dev/null +++ b/tools/objtool/tests/generic/test-export-symbol-for-modules.sh @@ -0,0 +1,39 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# EXPORT_SYMBOL_FOR_MODULES() puts a vmlinux symbol in a "module:<names>" +# namespace, and the module loader grants access by matching the importing +# module's name against that list. A livepatch module is never on the list, so +# referencing such a symbol with a normal relocation fails modpost, and if that +# is silenced, fails to load with "Unknown symbol". It needs a klp relocation, +# the same as an unexported symbol. +# +# Ordinary namespaces are not affected: copy_import_ns() propagates the patched +# object's import tags to the patch module, so a normal relocation works. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair cross_module.c + +sym=other_mod_func + +# Plain vmlinux export: a normal relocation is what we want. +export_syms "$sym" +run_diff +assert_no_klp_sym "$sym" + +# Ordinary namespace: still a normal relocation. +export_syms +add_exports_ns vmlinux MY_NS "$sym" +run_diff +assert_no_klp_sym "$sym" + +# module: namespace: has to become a klp relocation. +export_syms +add_exports_ns vmlinux module:kvm "$sym" +run_diff +assert_klp_sym "$sym" vmlinux +assert_section __klp_relocs.vmlinux + +pass "EXPORT_SYMBOL_FOR_MODULES symbol referenced with a klp relocation" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 33/58] objtool/klp: Add test for new references to exported symbols 2026-09-11 18:49 ` [PATCH 26/58] objtool/klp: Add test for unchecksummed input Song Liu ` (5 preceding siblings ...) 2026-09-11 18:50 ` [PATCH 32/58] objtool/klp: Add test for EXPORT_SYMBOL_FOR_MODULES references Song Liu @ 2026-09-11 18:50 ` Song Liu 2026-09-11 18:50 ` [PATCH 34/58] objtool/klp: Add test for empty x86 alternative replacements Song Liu ` (24 subsequent siblings) 31 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:50 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu A patch may introduce a reference the original object did not have. That is fine when the export belongs to vmlinux, and not fine when it belongs to a module: the livepatch would gain a module dependency nobody declared, and late module patching lets the patch load first. This tests the behavior of commit 72d76d0c18eb ("objtool/klp: Allow new references to module exports"). Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence <joe.lawrence@redhat.com> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../tests/generic/fixtures/new_export_ref.c | 20 +++++++++++ .../tests/generic/test-new-export-ref.sh | 34 +++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/new_export_ref.c create mode 100755 tools/objtool/tests/generic/test-new-export-ref.sh diff --git a/tools/objtool/tests/generic/fixtures/new_export_ref.c b/tools/objtool/tests/generic/fixtures/new_export_ref.c new file mode 100644 index 000000000000..c046b9f87d0b --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/new_export_ref.c @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A reference which only exists in the patched build. The symbol has no twin + * in the original object, so what klp diff may do with it depends entirely on + * whether Module.symvers says it is exported, and by what. + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +extern int newly_referenced(int x); + +int target(int x) +{ +#ifdef PATCHED + return newly_referenced(x); +#else + return x + 1; +#endif +} diff --git a/tools/objtool/tests/generic/test-new-export-ref.sh b/tools/objtool/tests/generic/test-new-export-ref.sh new file mode 100755 index 000000000000..ad6fa3979942 --- /dev/null +++ b/tools/objtool/tests/generic/test-new-export-ref.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# A reference the patch adds has no counterpart in the original object. klp +# diff used to reject any such reference needing a klp relocation, which ruled +# out patches that call something they did not call before -- a common enough +# thing for a fix to do. +# +# Module.symvers is what makes it safe: it says the symbol exists and who owns +# it. But that is only sufficient for a vmlinux export. A new reference to a +# module's export is a dependency the patch module does not declare, and the +# relocation would resolve only if that module happened to be loaded, so it +# stays an error. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair new_export_ref.c + +# Exported by vmlinux, in a module: namespace so it needs a klp relocation +# rather than an ordinary one. Allowed. +export_syms +add_exports_ns vmlinux module:kvm newly_referenced +run_diff +assert_klp_sym newly_referenced vmlinux + +# Exported by a module the patched object does not depend on. Rejected, and +# for that reason rather than some other. +export_syms +add_exports other_mod newly_referenced +run_diff 255 +assert_diff_log 'undeclared module dependency' + +pass "new reference allowed to a vmlinux export, rejected for a module export" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 34/58] objtool/klp: Add test for empty x86 alternative replacements 2026-09-11 18:49 ` [PATCH 26/58] objtool/klp: Add test for unchecksummed input Song Liu ` (6 preceding siblings ...) 2026-09-11 18:50 ` [PATCH 33/58] objtool/klp: Add test for new references to exported symbols Song Liu @ 2026-09-11 18:50 ` Song Liu 2026-09-11 18:50 ` [PATCH 35/58] objtool/klp: Add test for recorded checksum values Song Liu ` (23 subsequent siblings) 31 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:50 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu ALTERNATIVE_2("orig", "repl", ft1, "", ft2) produces a second entry whose replacement is empty. Its replacement offset still carries a relocation, but the label it points at is the end of the previous replacement -- which is also where the next one begins. The value is meaningless and only ever used with a length of zero, so cloning must not follow it. The first version of this fixture passed with the fix reverted, because the empty entry pointed at its own end label rather than at the neighbour's replacement. It has to reach into another function's replacement to distinguish the behaviour. This tests the behavior of commit 636f230ce21e ("objtool/klp: Ignore replacement offset of empty x86 alternatives"). Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence <joe.lawrence@redhat.com> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../tests/x86/fixtures/empty_alternative.c | 77 +++++++++++++++++++ .../tests/x86/test-empty-alternative.sh | 31 ++++++++ 2 files changed, 108 insertions(+) create mode 100644 tools/objtool/tests/x86/fixtures/empty_alternative.c create mode 100755 tools/objtool/tests/x86/test-empty-alternative.sh diff --git a/tools/objtool/tests/x86/fixtures/empty_alternative.c b/tools/objtool/tests/x86/fixtures/empty_alternative.c new file mode 100644 index 000000000000..9336d74bfa92 --- /dev/null +++ b/tools/objtool/tests/x86/fixtures/empty_alternative.c @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * An x86 alternative with an empty replacement, as the second entry of + * ALTERNATIVE_2("orig", "repl", ft1, "", ft2) produces. Its replacement + * offset still gets a relocation, but the label it points at is the end of the + * previous replacement, which is also where the *next* one begins -- here, + * neighbor()'s. The value is meaningless; it is only ever used with a length + * of zero. + * + * struct alt_instr is written out by hand so the fixture builds without kernel + * headers: s32 instr_offset, s32 repl_offset, u32 ft_flags, u8 instrlen, + * u8 replacementlen. The section carries an entsize because klp diff needs + * either that or an ANNOTATE_DATA_SPECIAL annotation to find entry boundaries. + * + * The replacement labels are global so the relocations name them rather than + * .altinstr_replacement plus an addend. + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +extern int neighbor_only(int x); + +int target(int x) +{ + asm volatile( + "661: nop\n\t" + ".pushsection .altinstr_replacement, \"ax\"\n\t" + ".globl target_repl\n\t" + "target_repl:\n\t" + " nop\n\t" + "target_repl_end:\n\t" + ".popsection\n\t" + ".pushsection .altinstructions, \"aM\", @progbits, 14\n\t" + /* a real replacement */ + ".long 661b - .\n\t" + ".long target_repl - .\n\t" + ".long 0\n\t" + ".byte 1\n\t" + ".byte target_repl_end - target_repl\n\t" + /* an empty one, pointing at neighbor()'s replacement */ + ".long 661b - .\n\t" + ".long neighbor_repl - .\n\t" + ".long 0\n\t" + ".byte 1\n\t" + ".byte 0\n\t" + ".popsection\n\t"); +#ifdef PATCHED + return x + 2; +#else + return x + 1; +#endif +} + +/* + * Unrelated, unpatched, and referencing a symbol nothing else does, so that + * dragging its replacement in is visible. + */ +int neighbor(int x) +{ + asm volatile( + "771: nop\n\t" + ".pushsection .altinstr_replacement, \"ax\"\n\t" + ".globl neighbor_repl\n\t" + "neighbor_repl:\n\t" + " call neighbor_only\n\t" + "neighbor_repl_end:\n\t" + ".popsection\n\t" + ".pushsection .altinstructions, \"aM\", @progbits, 14\n\t" + ".long 771b - .\n\t" + ".long neighbor_repl - .\n\t" + ".long 0\n\t" + ".byte 1\n\t" + ".byte neighbor_repl_end - neighbor_repl\n\t" + ".popsection\n\t"); + return x; +} diff --git a/tools/objtool/tests/x86/test-empty-alternative.sh b/tools/objtool/tests/x86/test-empty-alternative.sh new file mode 100755 index 000000000000..9d40c3a405af --- /dev/null +++ b/tools/objtool/tests/x86/test-empty-alternative.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# An x86 alternative with an empty replacement still gets a relocation for its +# replacement offset, but the label it points at is the end of the previous +# replacement -- which is also the start of the next one. The value is +# meaningless, and get_alt_entry() already ignores it. +# +# Cloning it drags in an unrelated neighboring replacement and everything that +# replacement references. In the reported case an empty alternative in +# meminfo_proc_show() pulled in one from proc_kcore_init(), emitting a klp +# relocation against init text which is long freed by the time the patch is +# applied. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair empty_alternative.c + +assert_input_section .altinstructions +assert_input_section .altinstr_replacement + +run_diff + +# target's own replacement comes along ... +assert_symbol target_repl +# ... neighbor's does not, nor what it references. +assert_no_symbol neighbor_repl +assert_no_symbol neighbor_only + +pass "empty alternative's replacement offset ignored when cloning" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 35/58] objtool/klp: Add test for recorded checksum values 2026-09-11 18:49 ` [PATCH 26/58] objtool/klp: Add test for unchecksummed input Song Liu ` (7 preceding siblings ...) 2026-09-11 18:50 ` [PATCH 34/58] objtool/klp: Add test for empty x86 alternative replacements Song Liu @ 2026-09-11 18:50 ` Song Liu 2026-09-11 18:50 ` [PATCH 36/58] objtool/klp: Add test for position-independent checksums Song Liu ` (22 subsequent siblings) 31 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:50 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu Whether klp diff treats a function as changed is decided by its checksum, and until now nothing looked at one. A test asserting only that the right functions were cloned cannot tell a correct checksum from one that happens to differ. Asserts both directions -- the changed function's checksum moves, the untouched one's does not -- and that checksumming identical input twice gives the same answer, since otherwise every rebuild reports spurious changes. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence <joe.lawrence@redhat.com> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../tests/generic/test-checksum-value.sh | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100755 tools/objtool/tests/generic/test-checksum-value.sh diff --git a/tools/objtool/tests/generic/test-checksum-value.sh b/tools/objtool/tests/generic/test-checksum-value.sh new file mode 100755 index 000000000000..feae6a12e98d --- /dev/null +++ b/tools/objtool/tests/generic/test-checksum-value.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# The per-function checksums klp checksum records are what klp diff uses to +# decide which functions changed. A checksum covering too little misses a real +# change and the patch silently omits the function; one covering too much, or +# unstable across identical input, clones functions nobody patched and drags +# their dependencies in with them. +# +# test-basic covers which functions got cloned, which is downstream of this and +# passes for either kind of wrong checksum as long as the two errors do not +# happen to cancel. This checks the checksums themselves. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair basic.c + +assert_input_symbol changed +assert_input_symbol untouched + +run_checksum + +# The edited function's checksum has to move, the untouched one's must not. +assert_checksum_differs changed +assert_checksum_matches untouched + +# And it has to be a function of the code, not of the build: checksumming the +# same input twice has to give the same answer, or every rebuild reports +# spurious changes. +first="$(checksum_of orig.o changed)" +build_pair basic.c +run_checksum +[ "$(checksum_of orig.o changed)" = "$first" ] || + fail "checksum for 'changed' differs between builds of identical source" + +pass "checksums track the changed function and are stable across rebuilds" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 36/58] objtool/klp: Add test for position-independent checksums 2026-09-11 18:49 ` [PATCH 26/58] objtool/klp: Add test for unchecksummed input Song Liu ` (8 preceding siblings ...) 2026-09-11 18:50 ` [PATCH 35/58] objtool/klp: Add test for recorded checksum values Song Liu @ 2026-09-11 18:50 ` Song Liu 2026-09-11 19:16 ` sashiko-bot 2026-09-11 18:50 ` [PATCH 37/58] objtool/klp: Add test for sympos in module objects Song Liu ` (21 subsequent siblings) 31 siblings, 1 reply; 77+ messages in thread From: Song Liu @ 2026-09-11 18:50 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu A function that only moves has not changed, and its checksum must not move with it. Otherwise every patch reports as changed everything that shifted because something ahead of it grew. The fixture is built with -fno-function-sections, overriding the harness default: with per-function sections every function sits at offset 0 of its own section and nothing ever moves, so the test would prove nothing. It also calls across to another function rather than looping within itself -- a loop branch keeps the same displacement wherever the function goes, so it is not position-dependent to begin with. This tests the behavior of commit cca84cb12908 ("objtool/klp: Fix position-dependent checksums for non-relocated jumps/calls"). Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence <joe.lawrence@redhat.com> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../generic/fixtures/checksum_position.c | 45 +++++++++++++++++++ .../tests/generic/test-checksum-position.sh | 35 +++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/checksum_position.c create mode 100755 tools/objtool/tests/generic/test-checksum-position.sh diff --git a/tools/objtool/tests/generic/fixtures/checksum_position.c b/tools/objtool/tests/generic/fixtures/checksum_position.c new file mode 100644 index 000000000000..e1362e58422b --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/checksum_position.c @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A function whose position in the section changes between the two builds, + * without the function itself changing. + * + * PATCHED adds a function ahead of it, so target() moves. It must be built + * without -ffunction-sections, or every function sits at offset 0 of its own + * section and nothing ever moves -- which is why the test passes + * -fno-function-sections. + * + * target() contains a loop, so it has a relative jump with no relocation. The + * offset encoded in that instruction depends on where the function sits, and + * hashing those bytes makes the checksum move when the function does. + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +__attribute__((noinline)) static int callee(int x) +{ + return x * 5 + 1; +} + +/* + * Inserted between callee() and target(), so the distance target's call has to + * encode changes. A jump or call within the same section needs no relocation: + * the displacement is in the instruction, and it is that displacement which + * moves. + */ +#ifdef PATCHED +__attribute__((noinline)) int padding(int x) +{ + int i, s = 0; + + for (i = 0; i < x; i++) + s += i * 3; + + return s; +} +#endif + +__attribute__((noinline)) int target(int x) +{ + return callee(x) + callee(x + 1); +} diff --git a/tools/objtool/tests/generic/test-checksum-position.sh b/tools/objtool/tests/generic/test-checksum-position.sh new file mode 100755 index 000000000000..459a718d75e7 --- /dev/null +++ b/tools/objtool/tests/generic/test-checksum-position.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# A function's checksum must not depend on where the function sits. +# +# A jump or call without a relocation encodes its target as an offset from the +# instruction. Hashing those bytes makes the checksum change whenever anything +# ahead of the function changes size -- so an unrelated edit elsewhere in the +# file reports this function as changed too, and the patch grows to include it +# and everything it references. Nothing fails; the livepatch is just larger and +# riskier than the patch it came from. +# +# Here the "patch" adds a function ahead of target() and changes nothing else. + +. "$(dirname "$0")/../lib.sh" + +setup + +# -fno-function-sections, or each function is at offset 0 of its own section +# and target() never moves. +build_pair checksum_position.c -fno-function-sections + +assert_input_symbol target + +# The fixture is only meaningful if target() actually moved. +orig_off="$(in_symbols orig.o | awk '$8 == "target" { print $2 }')" +new_off="$( in_symbols patched.o | awk '$8 == "target" { print $2 }')" +[ -n "$orig_off" ] && [ -n "$new_off" ] || + fail "target symbol not found in both objects" +[ "$orig_off" != "$new_off" ] || + probe_skip "compiler did not move target() between builds" + +assert_checksum_matches target + +pass "checksum unchanged when the function only moves" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* Re: [PATCH 36/58] objtool/klp: Add test for position-independent checksums 2026-09-11 18:50 ` [PATCH 36/58] objtool/klp: Add test for position-independent checksums Song Liu @ 2026-09-11 19:16 ` sashiko-bot 0 siblings, 0 replies; 77+ messages in thread From: sashiko-bot @ 2026-09-11 19:16 UTC (permalink / raw) To: Song Liu, live-patching Cc: puranjay, kernel-team, peterz, mbenes, jpoimboe, jikos, joe.lawrence, pmladek Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] The file header comment in checksum_position.c incorrectly claims that target() contains a loop, contradicting the implementation and commit message. -- commit 142ca8573cda7b552fbe79f475cedfd122ac600a Author: Song Liu <song@kernel.org> objtool/klp: Add test for position-independent checksums This commit introduces a test fixture and script to verify that a function's checksum remains consistent when its position shifts due to other code changes. The test uses a fixture built without function sections to verify the behavior of position-dependent checksums for non-relocated calls. > diff --git a/tools/objtool/tests/generic/fixtures/checksum_position.c b/tools/objtool/tests/generic/fixtures/checksum_position.c > new file mode 100644 > index 0000000000000..e1362e58422b7 > --- /dev/null > +++ b/tools/objtool/tests/generic/fixtures/checksum_position.c > @@ -0,0 +1,45 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * A function whose position in the section changes between the two builds, > + * without the function itself changing. > + * > + * PATCHED adds a function ahead of it, so target() moves. It must be built > + * without -ffunction-sections, or every function sits at offset 0 of its own > + * section and nothing ever moves -- which is why the test passes > + * -fno-function-sections. > + * > + * target() contains a loop, so it has a relative jump with no relocation. The > + * offset encoded in that instruction depends on where the function sits, and > + * hashing those bytes makes the checksum move when the function does. > + */ [Severity: Low] Is this file header comment accurate regarding target() containing a loop? The implementation of target() lower down in this file actually performs relative calls to callee(), rather than containing a loop. Furthermore, the commit message explicitly notes that a loop branch would keep the same displacement wherever the function goes, making it unsuitable for this test. Could this comment be updated to accurately reflect that target() uses relative calls across to another function instead of an internal loop? [ ... ] -- Sashiko AI review · https://sashiko.dev/#/patchset/20260911185031.1534046-1-song@kernel.org?part=36 ^ permalink raw reply [flat|nested] 77+ messages in thread
* [PATCH 37/58] objtool/klp: Add test for sympos in module objects 2026-09-11 18:49 ` [PATCH 26/58] objtool/klp: Add test for unchecksummed input Song Liu ` (9 preceding siblings ...) 2026-09-11 18:50 ` [PATCH 36/58] objtool/klp: Add test for position-independent checksums Song Liu @ 2026-09-11 18:50 ` Song Liu 2026-09-11 19:21 ` sashiko-bot 2026-09-11 18:50 ` [PATCH 38/58] objtool/klp: Add test for sympos resolved against a linked vmlinux Song Liu ` (20 subsequent siblings) 31 siblings, 1 reply; 77+ messages in thread From: Song Liu @ 2026-09-11 18:50 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu klp-sympos.c had no coverage at all. sympos disambiguates same-named symbols for livepatch, counting from 1, with 0 meaning the name is unique. Resolving to the wrong one is not a load failure -- it is a patch quietly wired to the wrong object. Covers the module path, where the position is a count in symbol table order and klp diff can work it out from the object alone. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence <joe.lawrence@redhat.com> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../tests/generic/fixtures/sympos_dup.c | 32 ++++++++++++ tools/objtool/tests/generic/test-sympos.sh | 51 +++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/sympos_dup.c create mode 100755 tools/objtool/tests/generic/test-sympos.sh diff --git a/tools/objtool/tests/generic/fixtures/sympos_dup.c b/tools/objtool/tests/generic/fixtures/sympos_dup.c new file mode 100644 index 000000000000..7eded9b12cfc --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/sympos_dup.c @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A static whose name recurs in every translation unit that includes it. + * Compiled once for a single-copy object and twice, partially linked, for one + * with duplicates -- which is the only case where sympos is non-zero. + * + * FUNC_NAME keeps the referencing functions distinct so both get patched. + * Only the first copy carries .modinfo; two would be a second thing to + * disambiguate and is not what this fixture is about. + */ + +#ifndef FUNC_NAME +#define FUNC_NAME use_a +#endif + +#ifndef NO_MODINFO +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; +#endif + +/* volatile so it survives as an STT_OBJECT rather than being folded away */ +static volatile int dup_counter = 1; + +int FUNC_NAME(int x) +{ + dup_counter += x; +#ifdef PATCHED + return dup_counter + 1; +#else + return dup_counter; +#endif +} diff --git a/tools/objtool/tests/generic/test-sympos.sh b/tools/objtool/tests/generic/test-sympos.sh new file mode 100755 index 000000000000..b71d4930a22a --- /dev/null +++ b/tools/objtool/tests/generic/test-sympos.sh @@ -0,0 +1,51 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# sympos is what livepatch uses to tell duplicate symbol names apart in the +# patched object: which "dup_counter" of several the relocation means. Get it +# wrong and the patch resolves to the wrong object at load time, silently. +# +# klp_find_sympos() reports 0 when a name is unique and a 1-based position when +# it is not, so both need checking -- always reporting a position, or never, +# each looks right in one of the two cases. +# +# This is the module path, counting symbol table order. vmlinux is reordered +# by the final link and goes through .klp.symid instead; that needs a linked +# vmlinux next to vmlinux.o and is not covered here. + +. "$(dirname "$0")/../lib.sh" + +setup + +# One copy: the name is unique, so there is nothing to disambiguate. +build_one sympos_dup.c orig.o -DFUNC_NAME=use_a +build_one sympos_dup.c patched.o -DFUNC_NAME=use_a -DPATCHED +run_diff + +assert_klp_sympos dup_counter 0 + +# Two copies: positions, in symbol table order. +for p in "" "-DPATCHED"; do + # shellcheck disable=SC2086 + build_one sympos_dup.c "a$p.o" -DFUNC_NAME=use_a $p + # shellcheck disable=SC2086 + build_one sympos_dup.c "b$p.o" -DFUNC_NAME=use_b -DNO_MODINFO $p +done +partial_link "$workdir/orig.o" "$workdir/a.o" "$workdir/b.o" || + probe_skip "partial link unavailable" +partial_link "$workdir/patched.o" "$workdir/a-DPATCHED.o" "$workdir/b-DPATCHED.o" || + probe_skip "partial link unavailable" + +# Without duplicates in the input there is nothing for sympos to number. +[ "$(count_input_symbols orig.o dup_counter)" = 2 ] || + fail "fixture did not produce two dup_counter symbols" + +run_diff + +assert_klp_sympos dup_counter 1 +assert_klp_sympos dup_counter 2 +# ... and nothing still claiming the name is unique +out_symbols | grep -qE '\.klp\.sym\.[^.]+\.dup_counter,0([[:space:]]|$)' && + fail "sympos 0 emitted for a duplicated symbol" + +pass "sympos numbers duplicate symbols and stays 0 for unique ones" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* Re: [PATCH 37/58] objtool/klp: Add test for sympos in module objects 2026-09-11 18:50 ` [PATCH 37/58] objtool/klp: Add test for sympos in module objects Song Liu @ 2026-09-11 19:21 ` sashiko-bot 0 siblings, 0 replies; 77+ messages in thread From: sashiko-bot @ 2026-09-11 19:21 UTC (permalink / raw) To: Song Liu, live-patching Cc: pmladek, puranjay, kernel-team, jikos, jpoimboe, mbenes, peterz, joe.lawrence Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: Pre-existing issues: - [High] `klp_find_sympos()` module path fails to filter undefined, unallocated, and .init symbols, leading to incorrect livepatch symbol resolution. -- commit 27e67aed6ae8f7baebc0b3436a1ab04c5f011800 Author: Song Liu <song@kernel.org> objtool/klp: Add test for sympos in module objects klp-sympos.c had no coverage at all. sympos disambiguates same-named symbols for livepatch, counting from 1, with 0 meaning the name is unique. Resolving to the wrong one is not a load failure -- it is a patch quietly wired to the wrong object. > diff --git a/tools/objtool/tests/generic/test-sympos.sh b/tools/objtool/tests/generic/test-sympos.sh > new file mode 100755 > index 0000000000000..b71d4930a22af > --- /dev/null > +++ b/tools/objtool/tests/generic/test-sympos.sh > @@ -0,0 +1,51 @@ [ ... ] > +# sympos is what livepatch uses to tell duplicate symbol names apart in the > +# patched object: which "dup_counter" of several the relocation means. Get it > +# wrong and the patch resolves to the wrong object at load time, silently. > +# > +# klp_find_sympos() reports 0 when a name is unique and a 1-based position when > +# it is not, so both need checking -- always reporting a position, or never, > +# each looks right in one of the two cases. [Severity: High] This is a pre-existing issue, but does klp_find_sympos() correctly filter out unallocated, undefined, or .init symbols when calculating the position? Looking at tools/objtool/klp-sympos.c:klp_find_sympos(), it appears to count all matching symbols without filtering: for_each_sym(elf, s) { if (!strcmp(s->name, sym->name)) { nr_matches++; if (s == sym) sympos = nr_matches; else has_dup = true; } } Because the kernel resolves symbols at runtime using core_kallsyms (which explicitly excludes SHN_UNDEF and .init sections), counting them in objtool could result in an incorrect sympos assignment. Could this mismatch cause the livepatch loader to wire the patch to the wrong target if an identically-named static symbol exists in an .init section or is undefined? -- Sashiko AI review · https://sashiko.dev/#/patchset/20260911185031.1534046-1-song@kernel.org?part=37 ^ permalink raw reply [flat|nested] 77+ messages in thread
* [PATCH 38/58] objtool/klp: Add test for sympos resolved against a linked vmlinux 2026-09-11 18:49 ` [PATCH 26/58] objtool/klp: Add test for unchecksummed input Song Liu ` (10 preceding siblings ...) 2026-09-11 18:50 ` [PATCH 37/58] objtool/klp: Add test for sympos in module objects Song Liu @ 2026-09-11 18:50 ` Song Liu 2026-09-11 18:50 ` [PATCH 39/58] objtool/klp: Add test for static locals which must not be correlated Song Liu ` (19 subsequent siblings) 31 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:50 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu vmlinux is not like a module: the final link reorders sub-sections, so a symbol's position has to come from the linked image rather than from symbol table order. klp diff bridges that with .klp.symid, and looks for it only when the object it was handed is called vmlinux.o with a vmlinux beside it. This was assigned to an end-to-end test on the assumption that it needs a real kernel build. It needs "ld -r" and "ld -e 0", and takes a fraction of a second. The fixture places the static appearing first in the symbol table at the higher address, and the link passes --sort-section=name to force the reordering the kernel's linker script performs. Without that the two ways of computing sympos agree, and a first version passed with the vmlinux path disabled. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence <joe.lawrence@redhat.com> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../tests/generic/fixtures/sympos_vmlinux.c | 40 +++++++++++++ .../tests/generic/test-sympos-vmlinux.sh | 57 +++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/sympos_vmlinux.c create mode 100755 tools/objtool/tests/generic/test-sympos-vmlinux.sh diff --git a/tools/objtool/tests/generic/fixtures/sympos_vmlinux.c b/tools/objtool/tests/generic/fixtures/sympos_vmlinux.c new file mode 100644 index 000000000000..d5e70994c582 --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/sympos_vmlinux.c @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Two translation units with a same-named static, placed so that the linker + * puts them in the opposite order to the one they appear in the symbol table. + * + * VARSEC selects the section the static lands in. Linking with + * --sort-section=name then orders them alphabetically rather than by object + * order, so the first symbol in the symbol table ends up at the *higher* + * address. That is the whole point: counting symbol table order and reading + * the linked image's addresses now give different answers, which is what makes + * it possible to tell which one klp diff used. + * + * Only use_a is patched, so exactly one sympos is emitted and there is nothing + * to attribute. + */ + +#ifndef FUNC_NAME +#define FUNC_NAME use_a +#endif +#ifndef VARSEC +#define VARSEC ".data.mmm" +#endif + +#ifndef NO_MODINFO +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; +#endif + +/* volatile so it survives as an STT_OBJECT rather than being folded away */ +static volatile int dup_counter __attribute__((section(VARSEC))) = 1; + +int FUNC_NAME(int x) +{ + dup_counter += x; +#ifdef PATCHED + return dup_counter + 1; +#else + return dup_counter; +#endif +} diff --git a/tools/objtool/tests/generic/test-sympos-vmlinux.sh b/tools/objtool/tests/generic/test-sympos-vmlinux.sh new file mode 100755 index 000000000000..b2b44001446f --- /dev/null +++ b/tools/objtool/tests/generic/test-sympos-vmlinux.sh @@ -0,0 +1,57 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# sympos for vmlinux, which is resolved differently from sympos for a module. +# +# A module's .ko preserves symbol table order, so klp diff can count -- that is +# what test-sympos covers. vmlinux cannot be counted: the final link reorders +# sub-sections, so the order in vmlinux.o is not the order the running kernel +# has. klp diff bridges that with .klp.symid, a table of { id, address } +# emitted into vmlinux.o whose addresses the linker resolves, read back out of +# the linked vmlinux. +# +# Getting it wrong points the relocation at a different symbol of the same +# name. Nothing fails to build or load; the patched code uses the wrong +# object. +# +# The fixture is arranged so the two answers differ: the static that comes +# first in the symbol table is placed at the *higher* address, so counting +# gives 1 and reading the linked image gives 2. Without that, both paths agree +# and the test cannot tell them apart. + +. "$(dirname "$0")/../lib.sh" + +setup + +# use_a's static sorts last by section name, use_b's first. Only use_a is +# patched, so exactly one sympos comes out. +build_one sympos_vmlinux.c orig_a.o -DFUNC_NAME=use_a -DVARSEC='".data.zzz"' +build_one sympos_vmlinux.c patched_a.o -DFUNC_NAME=use_a -DVARSEC='".data.zzz"' -DPATCHED +build_one sympos_vmlinux.c b.o -DFUNC_NAME=use_b -DVARSEC='".data.aaa"' -DNO_MODINFO + +make_vmlinux_pair "$workdir/orig_a.o" "$workdir/b.o" \ + -- "$workdir/patched_a.o" "$workdir/b.o" + +[ "$(count_input_symbols vmlinux.o dup_counter)" = 2 ] || + fail "fixture did not produce two dup_counter symbols" +has_input_section vmlinux.o .klp.symid || + fail "objtool --klp-symids emitted no .klp.symid table" +has_input_section vmlinux .klp.symid || + fail ".klp.symid did not survive the link" + +# The premise: symbol table order and address order must disagree, or the test +# proves nothing. +first_addr="$(in_symbols vmlinux | awk '$8 == "dup_counter" { print $2; exit }')" +low_addr="$(in_symbols vmlinux | awk '$8 == "dup_counter" { print $2 }' | sort | head -1)" +[ "$first_addr" != "$low_addr" ] || + probe_skip "linker did not reorder the two statics" + +assert_input_symbol dup_counter +run_diff + +# Address order says 2. Counting symbol table order would say 1. +assert_klp_sympos dup_counter 2 +out_symbols | grep -q 'dup_counter,1' && + fail "sympos 1 emitted: counted symbol table order instead of reading the linked vmlinux" + +pass "vmlinux sympos taken from the linked image, not from symbol table order" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 39/58] objtool/klp: Add test for static locals which must not be correlated 2026-09-11 18:49 ` [PATCH 26/58] objtool/klp: Add test for unchecksummed input Song Liu ` (11 preceding siblings ...) 2026-09-11 18:50 ` [PATCH 38/58] objtool/klp: Add test for sympos resolved against a linked vmlinux Song Liu @ 2026-09-11 18:50 ` Song Liu 2026-09-11 18:50 ` [PATCH 40/58] objtool/klp: Add test for __bug_table, __ex_table and __mcount_loc extraction Song Liu ` (18 subsequent siblings) 31 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:50 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu Most static locals have to be correlated so the patched code keeps using the running kernel's copy. Two kinds must not: anything in .data..once, the flag behind WARN_ONCE and friends, and the well-known names the kernel generates for per-instance things (__warned, __key, __func__). Sharing a .data..once flag means a patch inherits "already warned" from before it was applied, and the warning it was meant to surface never fires. The fixture deliberately does not name its .data..once variable __warned: the name rule would then catch it and the section rule would go untested. gcc spells these <var>.<id> and Clang <func>.<var>, so both are covered. This tests the behavior of commit ff529864e738 ("objtool/klp: Fix .data..once static local non-correlation") and commit 84c304a534b8 ("objtool/klp: Fix is_uncorrelated_static_local() for Clang"). Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence <joe.lawrence@redhat.com> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../fixtures/static_local_uncorrelated.c | 41 +++++++++++++++++++ .../generic/test-static-local-uncorrelated.sh | 40 ++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/static_local_uncorrelated.c create mode 100755 tools/objtool/tests/generic/test-static-local-uncorrelated.sh diff --git a/tools/objtool/tests/generic/fixtures/static_local_uncorrelated.c b/tools/objtool/tests/generic/fixtures/static_local_uncorrelated.c new file mode 100644 index 000000000000..cb4cdd7a496e --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/static_local_uncorrelated.c @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Static locals of three kinds, in one patched function. + * + * Most static locals must be correlated, so the patched code keeps using the + * running kernel's copy. Two kinds must not: + * + * - anything in .data..once, the flag behind WARN_ONCE and friends. Sharing + * it would mean a patch inherits "already warned" from before the patch. + * - the well-known names the kernel generates for such things (__warned, + * __key, __func__, ...), which are per-instance by nature. gcc names them + * <var>.<id> and Clang <func>.<var>, so both spellings have to be caught. + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +int target(int x) +{ + /* + * A .data..once variable whose name is *not* on the list below, so + * only the section can disqualify it. Naming it __warned would let + * the name rule catch it and the section rule go untested. + */ + static int once_flag __attribute__((section(".data..once"))); + /* a never-correlate name, in an ordinary section */ + static int __key; + /* and one that must be correlated */ + static int ordinary; + + if (!once_flag) + once_flag = 1; + __key += x; + ordinary += x; + +#ifdef PATCHED + return __key + ordinary + once_flag + 2; +#else + return __key + ordinary + once_flag + 1; +#endif +} diff --git a/tools/objtool/tests/generic/test-static-local-uncorrelated.sh b/tools/objtool/tests/generic/test-static-local-uncorrelated.sh new file mode 100755 index 000000000000..fa4ce88a79b1 --- /dev/null +++ b/tools/objtool/tests/generic/test-static-local-uncorrelated.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Some static locals must not be correlated with their counterparts in the +# running kernel; the patched code has to use a fresh copy instead. +# +# .data..once holds the "have we warned yet" flags behind WARN_ONCE. Correlate +# one and the patched function inherits the flag from before the patch, so the +# warning the patch was written to produce never fires. The same goes for the +# names the kernel generates for per-instance state -- __warned, __key, +# __func__ and friends. +# +# Both directions matter, so an ordinary static local is here too: a rule that +# refuses to correlate anything would pass a test that only checks the +# refusals. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair static_local_uncorrelated.c +run_diff + +# Compilers mangle static locals differently -- gcc gives __key.1, Clang +# target.__key -- so match on the base name. + +# Correlated: referenced through a klp symbol, pointing at the kernel's copy. +out_symbols | grep -q '\.klp\.sym\..*ordinary' || + fail "ordinary static local was not correlated" + +# Not correlated: no klp symbol, and a copy cloned into the patch instead. +out_symbols | grep -q '\.klp\.sym\..*__key' && + fail "__key was correlated; it must use a fresh copy" +out_sections | grep -qE '\.(bss|data)[^ ]*__key' || + fail "__key was neither correlated nor cloned" + +out_symbols | grep -q '\.klp\.sym\..*once_flag' && + fail ".data..once variable was correlated; it must use a fresh copy" +assert_section '.data..once' + +pass "per-instance static locals cloned, ordinary ones correlated" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 40/58] objtool/klp: Add test for __bug_table, __ex_table and __mcount_loc extraction 2026-09-11 18:49 ` [PATCH 26/58] objtool/klp: Add test for unchecksummed input Song Liu ` (12 preceding siblings ...) 2026-09-11 18:50 ` [PATCH 39/58] objtool/klp: Add test for static locals which must not be correlated Song Liu @ 2026-09-11 18:50 ` Song Liu 2026-09-11 19:20 ` sashiko-bot 2026-09-11 18:50 ` [PATCH 41/58] objtool/klp: Add test for kCFI prefix symbols and traps Song Liu ` (17 subsequent siblings) 31 siblings, 1 reply; 77+ messages in thread From: Song Liu @ 2026-09-11 18:50 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu Three special sections with no coverage. An entry that is not carried into the patch is not a build failure: the patched function simply loses its WARN_ON location, its exception fixup, or its ftrace callsite, and nobody finds out until one of them is needed. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence <joe.lawrence@redhat.com> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../tests/x86/fixtures/special_sections.c | 57 +++++++++++++++++++ .../tests/x86/test-special-sections.sh | 42 ++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 tools/objtool/tests/x86/fixtures/special_sections.c create mode 100755 tools/objtool/tests/x86/test-special-sections.sh diff --git a/tools/objtool/tests/x86/fixtures/special_sections.c b/tools/objtool/tests/x86/fixtures/special_sections.c new file mode 100644 index 000000000000..ad447e679bf8 --- /dev/null +++ b/tools/objtool/tests/x86/fixtures/special_sections.c @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A special section entry belonging to a patched function. SPECIAL_SEC picks + * which section, since klp diff treats eight of them alike and each needs + * extracting for the patched function and no other. + * + * The entry is written out by hand so the fixture builds without kernel + * headers. Only the leading relocation matters to klp diff; the rest is + * padded to the section's real entry size, because the entries have to be the + * right length for the boundaries between them to fall in the right places. + * + * SPECIAL_RELOCS covers __ex_table, whose entries relocate both the faulting + * instruction and its fixup; objtool rejects one with only the first. + */ + +#ifndef SPECIAL_SEC +#define SPECIAL_SEC "__bug_table" +#endif +#ifndef SPECIAL_ENTSIZE +#define SPECIAL_ENTSIZE 12 +#endif +#ifndef SPECIAL_RELOCS +#define SPECIAL_RELOCS 1 +#endif + +#define STR_(x) #x +#define STR(x) STR_(x) + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +int other(int x) +{ + return x + 9; +} + +int target(int x) +{ + asm volatile( + "1: nop\n\t" + "2:\n\t" + ".pushsection " SPECIAL_SEC ", \"aM\", @progbits, " + STR(SPECIAL_ENTSIZE) "\n\t" + ".long 1b - .\n\t" +#if SPECIAL_RELOCS > 1 + ".long 2b - .\n\t" + ".fill " STR(SPECIAL_ENTSIZE) " - 8, 1, 0\n\t" +#else + ".fill " STR(SPECIAL_ENTSIZE) " - 4, 1, 0\n\t" +#endif + ".popsection\n\t"); +#ifdef PATCHED + return x + 2; +#else + return x + 1; +#endif +} diff --git a/tools/objtool/tests/x86/test-special-sections.sh b/tools/objtool/tests/x86/test-special-sections.sh new file mode 100755 index 000000000000..8dfaa4fc9a36 --- /dev/null +++ b/tools/objtool/tests/x86/test-special-sections.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# klp diff extracts entries from eight special sections. Between them the +# existing tests reach .kcfi_traps, __jump_table, .static_call_sites and +# .altinstructions; __bug_table, __ex_table and __mcount_loc are covered by +# nothing, though the same extraction code serves all of them. +# +# Losing an entry is quiet in every case and wrong in a different way for each: +# a WARN() in patched code that no longer reports where it came from, an +# exception fixup that is simply not there when the faulting instruction traps, +# a function ftrace can no longer see. + +. "$(dirname "$0")/../lib.sh" + + +# section, entry size, relocations per entry +for spec in "__bug_table 12 1" "__ex_table 12 2" "__mcount_loc 8 1"; do + set -- $spec + sec=$1 + + # A fresh workdir per section: run_diff caches its checksums. + setup + build_pair special_sections.c \ + -DSPECIAL_SEC="\"$1\"" -DSPECIAL_ENTSIZE="$2" -DSPECIAL_RELOCS="$3" + + assert_input_section "$sec" + run_diff + + # Extracted, and pointing at the function that was patched. + assert_section "$sec" + assert_reloc_sym "$sec" target + assert_patched target + + # Nothing belonging to the function that was not. + assert_not_patched other + assert_no_reloc_sym "$sec" other + + cleanup +done + +pass "__bug_table, __ex_table and __mcount_loc entries extracted" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* Re: [PATCH 40/58] objtool/klp: Add test for __bug_table, __ex_table and __mcount_loc extraction 2026-09-11 18:50 ` [PATCH 40/58] objtool/klp: Add test for __bug_table, __ex_table and __mcount_loc extraction Song Liu @ 2026-09-11 19:20 ` sashiko-bot 0 siblings, 0 replies; 77+ messages in thread From: sashiko-bot @ 2026-09-11 19:20 UTC (permalink / raw) To: Song Liu, live-patching Cc: joe.lawrence, kernel-team, mbenes, pmladek, peterz, jikos, puranjay, jpoimboe Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] Test assertion for filtering unpatched functions evaluates vacuously because the control function emits no special section entries. -- commit 0a4615bf68e772413ad07a598da719d764e4517c Author: Song Liu <song@kernel.org> objtool/klp: Add test for __bug_table, __ex_table and __mcount_loc extraction This commit adds a test suite for extracting entries from three special sections using klp diff. The test verifies that patched functions retain their entries while unpatched functions do not. > diff --git a/tools/objtool/tests/x86/fixtures/special_sections.c b/tools/objtool/tests/x86/fixtures/special_sections.c > index 0000000000000..ad447e679bf82 > --- /dev/null > +++ b/tools/objtool/tests/x86/fixtures/special_sections.c [ ... ] > +static const char __modinfo[] > + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; > + > +int other(int x) > +{ > + return x + 9; > +} [Severity: Low] Does this control function emit the necessary entries to properly test filtering? The other() function in tools/objtool/tests/x86/fixtures/special_sections.c is used as the unpatched control case, but it lacks the inline assembly to emit a SPECIAL_SEC entry. Consequently, it generates no special section entries in the input object file. [ ... ] > diff --git a/tools/objtool/tests/x86/test-special-sections.sh b/tools/objtool/tests/x86/test-special-sections.sh > index 0000000000000..8dfaa4fc9a360 > --- /dev/null > +++ b/tools/objtool/tests/x86/test-special-sections.sh [ ... ] > + # Extracted, and pointing at the function that was patched. > + assert_section "$sec" > + assert_reloc_sym "$sec" target > + assert_patched target > + > + # Nothing belonging to the function that was not. > + assert_not_patched other > + assert_no_reloc_sym "$sec" other [Severity: Low] Will this negative test evaluate vacuously? When running tools/objtool/tests/x86/test-special-sections.sh, the test executes assert_no_reloc_sym "$sec" other. Because the unpatched function other() lacks special section entries in the input object file, there are no input relocations referencing other() for klp diff to filter out. This assertion will pass even if the tool's filtering logic is broken and copies all entries to the output, missing the intended verification. -- Sashiko AI review · https://sashiko.dev/#/patchset/20260911185031.1534046-1-song@kernel.org?part=40 ^ permalink raw reply [flat|nested] 77+ messages in thread
* [PATCH 41/58] objtool/klp: Add test for kCFI prefix symbols and traps 2026-09-11 18:49 ` [PATCH 26/58] objtool/klp: Add test for unchecksummed input Song Liu ` (13 preceding siblings ...) 2026-09-11 18:50 ` [PATCH 40/58] objtool/klp: Add test for __bug_table, __ex_table and __mcount_loc extraction Song Liu @ 2026-09-11 18:50 ` Song Liu 2026-09-11 19:21 ` sashiko-bot 2026-09-11 18:50 ` [PATCH 42/58] objtool/klp: Add test for symbols whose linkage the patch changes Song Liu ` (16 subsequent siblings) 31 siblings, 1 reply; 77+ messages in thread From: Song Liu @ 2026-09-11 18:50 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu Two properties, asserted separately because they fail differently: - the __cfi_<func> prefix symbol has to be carried with the patched function, or the function has no type identity and indirect calls to it trap; - its .kcfi_traps entry has to be extracted, or the trap is not recognised as a CFI failure and a clean report becomes an oops. test-special-section hand-assembles a .kcfi_traps entry, which is what makes it catch commit 7df1638df97b ("objtool/klp: Fix .kcfi_traps special section extraction"). Nothing until now built real kCFI code. This tests the behavior of commit da4326573ae8 ("objtool/klp: Fix kCFI trap handling"). Commit f7ceffd21a8a ("objtool/klp: Fix kCFI prefix finding/cloning") cannot be tested as such, its code having been rewritten by commit fe6a87e0abac ("objtool: Improve and simplify prefix symbol detection"); this guards the current implementation's behaviour instead. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence <joe.lawrence@redhat.com> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- tools/objtool/tests/x86/fixtures/kcfi.c | 39 +++++++++++++++++++++++++ tools/objtool/tests/x86/test-kcfi.sh | 39 +++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 tools/objtool/tests/x86/fixtures/kcfi.c create mode 100755 tools/objtool/tests/x86/test-kcfi.sh diff --git a/tools/objtool/tests/x86/fixtures/kcfi.c b/tools/objtool/tests/x86/fixtures/kcfi.c new file mode 100644 index 000000000000..52cbb0fe6df2 --- /dev/null +++ b/tools/objtool/tests/x86/fixtures/kcfi.c @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * An indirect call, which under kCFI is preceded by a type check and a trap. + * + * Clang emits a __cfi_<func> prefix symbol carrying the type hash ahead of + * every address-taken function, and records the trap site in .kcfi_traps. + * Both belong to the function and both have to come with it into a patch. + * + * Needs -fsanitize=kcfi, which only Clang has. + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +static int impl_a(int x) +{ + return x + 1; +} + +static int impl_b(int x) +{ + return x * 2; +} + +int (*pick(int x))(int) +{ + return (x & 1) ? impl_a : impl_b; +} + +int target(int x) +{ + int (*fn)(int arg) = pick(x); + +#ifdef PATCHED + return fn(x) + 2; +#else + return fn(x) + 1; +#endif +} diff --git a/tools/objtool/tests/x86/test-kcfi.sh b/tools/objtool/tests/x86/test-kcfi.sh new file mode 100755 index 000000000000..b583ef608747 --- /dev/null +++ b/tools/objtool/tests/x86/test-kcfi.sh @@ -0,0 +1,39 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Under kCFI an indirect call checks a type hash before jumping, and traps on a +# mismatch. Two things belong to the calling function and must come with it +# into a patch: +# +# - the __cfi_<func> prefix symbol holding the hash. Lose it and the patched +# function has no type identity, so indirect calls to it trap. +# - its .kcfi_traps entry. Lose that and the trap is not recognised as a +# CFI failure, so what should be a clean report becomes an oops. +# +# Neither shows up at build time. + +. "$(dirname "$0")/../lib.sh" + +clang_only "kCFI is a Clang feature" + +setup + +# Declared above that this is Clang's; a given Clang may still be too old. +cc_supports -fsanitize=kcfi || + probe_skip "this clang does not support -fsanitize=kcfi" + +build_pair kcfi.c -fsanitize=kcfi + +assert_input_section .kcfi_traps +assert_input_symbol __cfi_target + +run_diff + +assert_patched target + +# The prefix symbol comes with its function ... +assert_symbol __cfi_target +# ... and so does the trap entry. +assert_section .kcfi_traps + +pass "kCFI prefix symbol and trap entry carried with the patched function" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* Re: [PATCH 41/58] objtool/klp: Add test for kCFI prefix symbols and traps 2026-09-11 18:50 ` [PATCH 41/58] objtool/klp: Add test for kCFI prefix symbols and traps Song Liu @ 2026-09-11 19:21 ` sashiko-bot 0 siblings, 0 replies; 77+ messages in thread From: sashiko-bot @ 2026-09-11 19:21 UTC (permalink / raw) To: live-patching, Song Liu Cc: peterz, jpoimboe, jikos, joe.lawrence, mbenes, puranjay, kernel-team, pmladek Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] The compiler may optimize away the indirect call in `target()`, which would eliminate the expected `.kcfi_traps` entry and break the test on newer or more aggressive Clang versions. -- commit ab08ca5f9689c4205bf1341ad94b14e2747fd94b Author: Song Liu <song@kernel.org> objtool/klp: Add test for kCFI prefix symbols and traps This commit adds a test case to verify that the __cfi_<func> prefix symbol and its .kcfi_traps entry are correctly handled and carried into a patch for kCFI-enabled builds. > diff --git a/tools/objtool/tests/x86/fixtures/kcfi.c b/tools/objtool/tests/x86/fixtures/kcfi.c > new file mode 100644 > index 0000000000000..52cbb0fe6df2a > --- /dev/null > +++ b/tools/objtool/tests/x86/fixtures/kcfi.c [ ... ] > +int (*pick(int x))(int) > +{ > + return (x & 1) ? impl_a : impl_b; > +} [Severity: Low] Will the compiler aggressively inline this function into target()? Lacking a noinline attribute, this function can be fully inlined, exposing the function pointers for devirtualization. > + > +int target(int x) > +{ > + int (*fn)(int arg) = pick(x); > + > +#ifdef PATCHED > + return fn(x) + 2; [Severity: Low] Could aggressive compiler optimizations devirtualize this indirect call? The indirect call fn(x) relies on pick() and its targets not being inlined to remain an indirect call in the final object code. If devirtualized, the expected .kcfi_traps entry would be removed, potentially causing the test to spuriously fail on newer or more aggressive Clang versions. > +#else > + return fn(x) + 1; > +#endif > +} -- Sashiko AI review · https://sashiko.dev/#/patchset/20260911185031.1534046-1-song@kernel.org?part=41 ^ permalink raw reply [flat|nested] 77+ messages in thread
* [PATCH 42/58] objtool/klp: Add test for symbols whose linkage the patch changes 2026-09-11 18:49 ` [PATCH 26/58] objtool/klp: Add test for unchecksummed input Song Liu ` (14 preceding siblings ...) 2026-09-11 18:50 ` [PATCH 41/58] objtool/klp: Add test for kCFI prefix symbols and traps Song Liu @ 2026-09-11 18:50 ` Song Liu 2026-09-11 18:50 ` [PATCH 43/58] objtool/klp: Test rejection of a file-local static branch key Song Liu ` (15 subsequent siblings) 31 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:50 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu A patch can move a symbol between static and global without renaming it: dropping "static" from a helper so something else can call it, or adding it to one that is no longer shared. Correlation keys off more than the name, so a symbol whose binding moved has to still pair with itself. Failing to is not a build failure. The symbol looks new, and a new data symbol is either rejected or cloned as a second copy -- at which point the patched code updates its own private variable while the rest of the kernel keeps reading the original. The test covers both directions in one fixture, a function going global and a variable going static, and asserts the outcome rather than the absence of a warning: each symbol resolves back to the kernel's copy through a klp symbol, and neither is cloned into the patch. An earlier version asserted only that no "no correlation" or "changed data" message appeared, and passed with correlation deliberately broken. What the messages say and what the patch contains are not the same question. Verified to fail with correlation made to require matching symbol bindings. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence <joe.lawrence@redhat.com> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../tests/generic/fixtures/local_to_global.c | 34 +++++++++++++ .../generic/test-local-to-global-flip.sh | 50 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/local_to_global.c create mode 100755 tools/objtool/tests/generic/test-local-to-global-flip.sh diff --git a/tools/objtool/tests/generic/fixtures/local_to_global.c b/tools/objtool/tests/generic/fixtures/local_to_global.c new file mode 100644 index 000000000000..3c9eb9200ce5 --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/local_to_global.c @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A function which the patch changes from static to non-static, and a variable + * that goes the other way. The names are unchanged; only the binding moves. + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +/* noinline, or the static one is folded into its caller and has no symbol */ +#ifdef PATCHED +__attribute__((noinline)) int flipped_up(int x) /* was static */ +#else +__attribute__((noinline)) static int flipped_up(int x) +#endif +{ + return x + 1; +} + +#ifdef PATCHED +static volatile int flipped_down = 5; /* was global */ +#else +volatile int flipped_down = 5; +#endif + +int caller(int x) +{ + flipped_down += x; +#ifdef PATCHED + return flipped_up(x) + flipped_down + 2; +#else + return flipped_up(x) + flipped_down + 1; +#endif +} diff --git a/tools/objtool/tests/generic/test-local-to-global-flip.sh b/tools/objtool/tests/generic/test-local-to-global-flip.sh new file mode 100755 index 000000000000..b4dca6cff0f2 --- /dev/null +++ b/tools/objtool/tests/generic/test-local-to-global-flip.sh @@ -0,0 +1,50 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# A patch can change a symbol's linkage without renaming it: dropping "static" +# from a helper so something else can call it, or adding it to one that is no +# longer shared. Correlation keys off more than the name, so a symbol whose +# binding moved can fail to pair with itself. +# +# Failing to correlate is not a build failure. The symbol looks new, and a +# "new" data symbol is either rejected or cloned as a second copy -- at which +# point the patched code updates its own private variable and the rest of the +# kernel keeps reading the original. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair local_to_global.c + +# Confirm the fixture really moved the bindings, in both directions. +in_symbols orig.o | grep -qE 'LOCAL.*flipped_up' || + fail "flipped_up is not local in the original" +in_symbols patched.o | grep -qE 'GLOBAL.*flipped_up' || + fail "flipped_up is not global in the patched object" +in_symbols orig.o | grep -qE 'GLOBAL.*flipped_down' || + fail "flipped_down is not global in the original" +in_symbols patched.o | grep -qE 'LOCAL.*flipped_down' || + fail "flipped_down is not local in the patched object" + +run_diff + +assert_diff_log 'changed function: caller' + +# Correlated means each pairs with its own counterpart in the original, so the +# patch refers back to the kernel's copy ... +assert_klp_sym flipped_up vmlinux +assert_klp_sym flipped_down vmlinux + +# ... rather than carrying its own. A second copy of flipped_down is the bad +# outcome: patched code would update its private one while the rest of the +# kernel keeps reading the original. +assert_not_patched flipped_up +assert_no_section .data.flipped_down +assert_no_section .bss.flipped_down + +diff_log | grep -q 'no correlation' && + fail "linkage change reported as an uncorrelated symbol" +diff_log | grep -q 'changed data' && + fail "linkage change reported as changed data" + +pass "symbols correlated across a change of linkage" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 43/58] objtool/klp: Test rejection of a file-local static branch key 2026-09-11 18:49 ` [PATCH 26/58] objtool/klp: Add test for unchecksummed input Song Liu ` (15 preceding siblings ...) 2026-09-11 18:50 ` [PATCH 42/58] objtool/klp: Add test for symbols whose linkage the patch changes Song Liu @ 2026-09-11 18:50 ` Song Liu 2026-09-11 18:50 ` [PATCH 44/58] objtool/klp: Test a hand-built livepatch module's static call keys Song Liu ` (14 subsequent siblings) 31 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:50 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu A static branch key owned by a module cannot be reached with a klp reloc: late module patching allows the livepatch module to load first, leaving the __jump_table entry unresolved for jump_label_add_module() to dereference. validate_special_section_klp_reloc() rejects it at build time. test-jump-label-module-key covers that for a global key. A file-local one takes a different route to the same check: the compiler references a static through its section symbol plus an addend, so the key has to be resolved from the section before it can be recognised as STT_OBJECT at all. Until commit f9fb44b0ecef ("objtool/klp: Fix detection of corrupt static branch/call entries") it was not, and the reference was silently emitted. Give the fixture a STATIC_KEY knob and cover it. The test checks that the input really does reference the key through its section, since without that it is only a second copy of the existing test. Verified by reverting commit f9fb44b0ecef ("objtool/klp: Fix detection of corrupt static branch/call entries"): klp diff accepts the input and the test fails, under both gcc and clang. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence <joe.lawrence@redhat.com> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../test-jump-label-module-static-key.sh | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100755 tools/objtool/tests/generic/test-jump-label-module-static-key.sh diff --git a/tools/objtool/tests/generic/test-jump-label-module-static-key.sh b/tools/objtool/tests/generic/test-jump-label-module-static-key.sh new file mode 100755 index 000000000000..7bdcbaf2fa60 --- /dev/null +++ b/tools/objtool/tests/generic/test-jump-label-module-static-key.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# A static branch key owned by a module is rejected whether the key is global +# or file-local. +# +# The rejection matters because late module patching allows the livepatch +# module to load before the module it depends on: the __jump_table klp reloc is +# then unresolved, and jump_label_add_module() dereferences an uninitialized +# pointer. Catching it at build time is the only defence. +# +# test-jump-label-module-key covers the global key. A file-local one reaches +# the same check by a different route: the compiler emits the reference against +# the section symbol plus an addend, so validate_special_section_klp_reloc() +# has to resolve it to the underlying object before it can see a key at all. +# Until it did, a static key was passed over as "not STT_OBJECT" and the +# unsupported reference was emitted with nothing said. +# +# Fixed by f9fb44b0ecef ("objtool/klp: Fix detection of corrupt static +# branch/call entries"). + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair jump_label.c -DSTATIC_KEY -DMODNAME='"klp_testmod"' + +require_input_section __jump_table + +# The premise: the key is reached through its section symbol, not by name. +# Without that this is just a second copy of test-jump-label-module-key. +input_jump_relocs="$(in_relocs orig.o | awk '/rela__jump_table/,/^$/')" + +echo "$input_jump_relocs" | grep -q klp_test_key || + fail "fixture produced no __jump_table reference to the key" +echo "$input_jump_relocs" | grep -qE '\.(bss|data)\.klp_test_key' || + probe_skip "compiler referenced the static key by name, not through its section" + +run_diff 255 + +diff_log | grep -q 'unsupported static branch key klp_test_key' || + fail "expected rejection, got: $(diff_log | tail -1)" +[ -e "$workdir/out.o" ] && + fail "output object produced for a rejected input" + +pass "module-owned file-local static branch key rejected" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 44/58] objtool/klp: Test a hand-built livepatch module's static call keys 2026-09-11 18:49 ` [PATCH 26/58] objtool/klp: Add test for unchecksummed input Song Liu ` (16 preceding siblings ...) 2026-09-11 18:50 ` [PATCH 43/58] objtool/klp: Test rejection of a file-local static branch key Song Liu @ 2026-09-11 18:50 ` Song Liu 2026-09-11 18:50 ` [PATCH 45/58] objtool/klp: Test text annotations on alternative replacements Song Liu ` (13 subsequent siblings) 31 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:50 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu __SCK__* static call keys are not exported; modules are given read-only access at load time. Livepatch modules built by klp-build do have full access to theirs, and commit 164c9201e1da ("objtool: Add base objtool support for livepatch modules") added a check on that basis -- but a livepatch module can also be written by hand, as everything under samples/livepatch is, and such a module hits an unexported key as soon as it does anything expanding to a static call. With CONFIG_MEM_ALLOC_PROFILING_DEBUG that includes allocating memory, which is how livepatch-shadow-fix1 came to fail to build. Cover it, with the plain module as a control: it takes the same path and has always been accepted, so a test that built only the livepatch variant could not tell this fix from the check being deleted. This is objtool's ordinary check pass rather than a klp subcommand, which is the first test here to exercise it -- and is the point, since that pass is what runs over a hand-built livepatch module during a normal kernel build. Verified by reverting commit f495054bd12e ("objtool/klp: Fix unexported static call key access for manually built livepatch modules"): objtool reports "can't find static_call_key symbol: __SCK__klp_test_call" and the test fails, under both gcc and clang. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence <joe.lawrence@redhat.com> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../tests/x86/fixtures/static_call_no_key.c | 32 +++++++++++++++ .../tests/x86/test-manual-klp-static-call.sh | 40 +++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 tools/objtool/tests/x86/fixtures/static_call_no_key.c create mode 100755 tools/objtool/tests/x86/test-manual-klp-static-call.sh diff --git a/tools/objtool/tests/x86/fixtures/static_call_no_key.c b/tools/objtool/tests/x86/fixtures/static_call_no_key.c new file mode 100644 index 000000000000..748ba7c0f86d --- /dev/null +++ b/tools/objtool/tests/x86/fixtures/static_call_no_key.c @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A static call to a trampoline whose key symbol this object cannot see. + * + * That is the normal situation for a module: __SCK__* keys are not exported, + * and read-only access is granted at load time instead. objtool's static call + * handling has to accept it for any module, including a livepatch module built + * by hand rather than by klp-build. + * + * LIVEPATCH adds the .modinfo tag which makes objtool treat this as a + * livepatch module. + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = +#ifdef LIVEPATCH + "\0livepatch=Y" +#endif + "\0name=klp_testmod"; + +/* + * The trampoline is undefined here, exactly as it is for a module calling a + * static call defined in vmlinux. No __SCK__klp_test_call accompanies it. + */ +extern void __SCT__klp_test_call(void); + +int target(int x) +{ + __asm__ volatile("call __SCT__klp_test_call\n\t" ::: "memory"); + + return x + 1; +} diff --git a/tools/objtool/tests/x86/test-manual-klp-static-call.sh b/tools/objtool/tests/x86/test-manual-klp-static-call.sh new file mode 100755 index 000000000000..6c4d275e3549 --- /dev/null +++ b/tools/objtool/tests/x86/test-manual-klp-static-call.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# objtool's static call handling must accept a livepatch module which cannot +# see a static call's key symbol. +# +# __SCK__* keys are not exported; modules get read-only access at load time +# instead. Livepatch modules built by klp-build do have full access to their +# keys, and a check was added on the strength of that -- but a livepatch module +# can also be written by hand, and samples/livepatch is full of them. One of +# those needs a key it cannot see as soon as it does anything that expands to a +# static call, which with CONFIG_MEM_ALLOC_PROFILING_DEBUG includes allocating +# memory: +# +# samples/livepatch/livepatch-shadow-fix1.o: error: objtool: static_call: +# can't find static_call_key symbol: __SCK__WARN_trap +# +# The module built without the livepatch tag is the control: it takes the same +# path and has always been accepted, so a test which only built the livepatch +# one could not tell this fix from the check being removed altogether. +# +# Fixed by f495054bd12e ("objtool/klp: Fix unexported static call key access +# for manually built livepatch modules"). + +. "$(dirname "$0")/../lib.sh" + +setup + +# Not a klp subcommand: this is objtool's ordinary check pass, which is what +# runs over a hand-built livepatch module during a normal kernel build. +for tag in "" -DLIVEPATCH; do + build_one static_call_no_key.c mod.o $tag + + "$OBJTOOL" --module --static-call "$workdir/mod.o" \ + > "$workdir/objtool.log" 2>&1 || + fail "objtool rejected a ${tag:+livepatch }module which cannot" \ + "see its static call key: $(tail -1 "$workdir/objtool.log")" +done + +pass "livepatch module accepted without access to its static call key" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 45/58] objtool/klp: Test text annotations on alternative replacements 2026-09-11 18:49 ` [PATCH 26/58] objtool/klp: Add test for unchecksummed input Song Liu ` (17 preceding siblings ...) 2026-09-11 18:50 ` [PATCH 44/58] objtool/klp: Test a hand-built livepatch module's static call keys Song Liu @ 2026-09-11 18:50 ` Song Liu 2026-09-11 18:50 ` [PATCH 46/58] objtool/klp: Add test for data object checksums Song Liu ` (12 subsequent siblings) 31 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:50 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu The kernel annotates instructions inside an ALTERNATIVE's replacement wherever objtool has to be told something about them -- a retpoline-safe indirect branch, a deliberately absent ENDBR. klp diff dropped those annotations, for two reasons: replacement code has no real symbol, so the NOTYPE fake symbol objtool invents for it was not recognised as worth keeping a reference to, and .discard.annotate_insn was processed before .altinstructions, so the replacement it named had no clone yet. Nothing fails at build time when the annotation goes missing. It surfaces later as objtool warning about, or rejecting, the very code the annotation was there to explain. The fixture keeps the replacement label global, as empty_alternative.c does, so the relocations name it instead of .altinstr_replacement plus an addend -- which klp diff rejects outright. The label is still NOTYPE, which is the property under test. The test asserts the relocation as well as the section, since an entry whose relocation was dropped would otherwise pass. Verified by reverting commit 62a7a01fde87 ("objtool/klp: Fix extraction of text annotations for alternatives"): the output has no .discard.annotate_insn at all and the test fails, under both gcc and clang. Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../objtool/tests/x86/fixtures/alt_annotate.c | 57 +++++++++++++++++++ .../objtool/tests/x86/test-alt-annotation.sh | 38 +++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 tools/objtool/tests/x86/fixtures/alt_annotate.c create mode 100755 tools/objtool/tests/x86/test-alt-annotation.sh diff --git a/tools/objtool/tests/x86/fixtures/alt_annotate.c b/tools/objtool/tests/x86/fixtures/alt_annotate.c new file mode 100644 index 000000000000..af44d320dcb0 --- /dev/null +++ b/tools/objtool/tests/x86/fixtures/alt_annotate.c @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * An x86 alternative whose replacement instruction carries a text annotation. + * + * The kernel does this wherever an ALTERNATIVE contains something objtool has + * to be told about -- a retpoline-safe indirect branch, an intentionally + * missing ENDBR -- so the .discard.annotate_insn entry references an address + * inside .altinstr_replacement rather than inside a function. + * + * Two things make that awkward for klp diff, and both are why this fixture + * exists. Replacement code has no real symbol: objtool invents a NOTYPE fake + * symbol for it, so an annotation pointing there does not reference a FUNC. + * And .discard.annotate_insn has to be cloned after .altinstructions, or the + * replacement it names has no clone to point at yet. + * + * struct alt_instr is written out by hand as in empty_alternative.c: s32 + * instr_offset, s32 repl_offset, u32 ft_flags, u8 instrlen, u8 replacementlen, + * with an entsize so klp diff can find the entry boundaries. + * .discard.annotate_insn entries are s32 offset, s32 type; type 2 is + * ANNOTYPE_RETPOLINE_SAFE. + * + * The replacement label is global so the relocations name it rather than + * .altinstr_replacement plus an addend, which klp diff cannot convert. It is + * still NOTYPE, which is the shape that matters here. + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +int target(int x) +{ + asm volatile( + "661: nop\n\t" + ".pushsection .altinstr_replacement, \"ax\"\n\t" + ".globl target_repl\n\t" + "target_repl:\n\t" + " nop\n\t" + /* The annotation lands inside the replacement. */ + ".pushsection .discard.annotate_insn, \"M\", @progbits, 8\n\t" + ".long target_repl - .\n\t" + ".long 2\n\t" + ".popsection\n\t" + "target_repl_end:\n\t" + ".popsection\n\t" + ".pushsection .altinstructions, \"aM\", @progbits, 14\n\t" + ".long 661b - .\n\t" + ".long target_repl - .\n\t" + ".long 0\n\t" + ".byte 1\n\t" + ".byte target_repl_end - target_repl\n\t" + ".popsection\n\t"); +#ifdef PATCHED + return x + 2; +#else + return x + 1; +#endif +} diff --git a/tools/objtool/tests/x86/test-alt-annotation.sh b/tools/objtool/tests/x86/test-alt-annotation.sh new file mode 100755 index 000000000000..96760e6df9e5 --- /dev/null +++ b/tools/objtool/tests/x86/test-alt-annotation.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# A text annotation on an instruction inside an alternative's replacement must +# be carried into the patch. +# +# The kernel annotates replacement code wherever objtool has to be told +# something about it -- a retpoline-safe indirect branch, a deliberately absent +# ENDBR. Two things made klp diff drop those annotations: +# +# - replacement code has no real symbol, so objtool invents a NOTYPE fake +# one, and the extraction only kept references to FUNC symbols; +# - .discard.annotate_insn was processed before .altinstructions, so the +# replacement it referenced had no clone to point at yet. +# +# Nothing fails at build time when the annotation goes missing. It surfaces +# later as objtool warning about, or rejecting, the patched code it was there +# to explain. +# +# Fixed by 62a7a01fde87 ("objtool/klp: Fix extraction of text annotations for +# alternatives"). + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair alt_annotate.c + +assert_input_section .altinstructions +assert_input_section .discard.annotate_insn + +run_diff + +# The annotation has to survive, and to still name the replacement. Checking +# only the section would pass on an entry whose relocation was dropped. +assert_section .discard.annotate_insn +assert_reloc_sym .discard.annotate_insn target_repl + +pass "text annotation on an alternative replacement carried into the patch" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 46/58] objtool/klp: Add test for data object checksums 2026-09-11 18:49 ` [PATCH 26/58] objtool/klp: Add test for unchecksummed input Song Liu ` (18 preceding siblings ...) 2026-09-11 18:50 ` [PATCH 45/58] objtool/klp: Test text annotations on alternative replacements Song Liu @ 2026-09-11 18:50 ` Song Liu 2026-09-11 18:50 ` [PATCH 47/58] objtool/klp: Add test for symbols with no checksum entry of their own Song Liu ` (11 subsequent siblings) 31 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:50 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu klp checksum hashes a data symbol's length, its bytes, and every relocation it carries -- as the target's name and adjusted addend, except a reference into a string section, which contributes the string's contents instead. Nothing covered any of it. Each is load-bearing, and the failure is always the same shape: a checksum which ignores one calls a changed object unchanged, klp diff leaves it out of the patch, and the patched code goes on reading the kernel's old copy. The string case cannot be caught by hashing bytes: the pointer is identical, same section and same offset, and only the text it refers to moved. One fixture, six variants applied to the patched build alone. Each was verified by sabotaging the line it covers and watching the test fail: raw bytes initialiser change length a .bss object grows; its bytes are never hashed string contents literal edited in place, pointer untouched reloc target name pointer moved to another function reloc addend same array, different index section-symbol path the same, via a static's section symbol Two of those needed the fixture rebuilding. An initialised array does not isolate the length, because growing one changes the hashed bytes too -- hence .bss, where there are none. And a named char[] does not reach the contents-hashing path at all: that keys on SHF_STRINGS, which the compiler sets on the mergeable section a literal lands in and not on an array given a section of its own. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence <joe.lawrence@redhat.com> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../tests/generic/fixtures/checksum_data.c | 116 ++++++++++++++++++ .../tests/generic/test-checksum-data.sh | 61 +++++++++ 2 files changed, 177 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/checksum_data.c create mode 100755 tools/objtool/tests/generic/test-checksum-data.sh diff --git a/tools/objtool/tests/generic/fixtures/checksum_data.c b/tools/objtool/tests/generic/fixtures/checksum_data.c new file mode 100644 index 000000000000..6310af5c02d3 --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/checksum_data.c @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Data objects whose checksums must move for reasons the raw bytes do not + * show. + * + * checksum_update_object() hashes a data symbol's length and its bytes, and + * then walks its relocations: a reference into a string section contributes + * the string's *contents*, and any other reference contributes the target + * symbol's name and the adjusted addend. So three changes that leave the + * object's own bytes identical still have to change its checksum: + * + * Each variant is selected by a -D on the patched build only, so the original + * is always the baseline: + * + * WHICH_FUNC the function pointer points somewhere else + * WHICH_STR the string pointer points at a different literal + * STR_CONTENT the string it points at is edited in place + * WHICH_SLOT the same array, at a different index: addend only + * WHICH_PRIV likewise, but a static, reached through its section symbol + * + * The last is the interesting one. Nothing in the pointer changes -- same + * section, same offset -- so a checksum that hashed only the relocation and + * not what it referred to would call the object unchanged, and the patched + * kernel would keep the old string. + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +int callee_a(int x); +int callee_b(int x); +int callee_a(int x) { return x + 1; } +int callee_b(int x) { return x + 2; } + +/* + * String *literals*, not named arrays. The contents-hashing path keys on + * SHF_STRINGS, which the compiler sets on the mergeable .rodata.str1.1 a + * literal lands in and not on a named char[] given a section of its own. A + * fixture using the latter exercises the ordinary name-and-addend path and + * reports nothing when the text changes. + */ +#if defined(PATCHED) && defined(STR_CONTENT) +#define MESSAGE "edited" +#else +#define MESSAGE "original" +#endif + +/* A plain data object: only its own bytes decide the checksum. */ +#if defined(PATCHED) && defined(PLAIN_VALUE) +int plain = 43; +#else +int plain = 42; +#endif + +/* + * A .bss object, where length is the only thing there is to hash: the section + * has no data, so the bytes are skipped and only sym->len distinguishes this + * from an object of another size. An initialised array would not isolate it + * -- growing one changes the hashed bytes as well. + */ +#if defined(PATCHED) && defined(LONGER) +char sized[4]; +#else +char sized[2]; +#endif + +/* + * A reference into the middle of an array: same target symbol, different + * addend. Nothing else in the object changes, so this is the only way to see + * whether the addend is hashed at all. + */ +int slots[4]; + +/* + * A file-local array. A reference to a static lands on its section symbol + * plus an offset, so the hash has to resolve that back to the underlying + * object before it has a name to hash at all -- a different code path from the + * global above, and one that silently contributes nothing when it fails. + */ +static int priv_slots[4]; + +struct desc { + int (*fn)(int arg); + const char *str; + int *slot; + int *priv; +}; + +const struct desc descriptor = { +#if defined(PATCHED) && defined(WHICH_FUNC) + .fn = callee_b, +#else + .fn = callee_a, +#endif +#if defined(PATCHED) && defined(WHICH_STR) + .str = "a different literal", +#else + .str = MESSAGE, +#endif +#if defined(PATCHED) && defined(WHICH_SLOT) + .slot = &slots[2], +#else + .slot = &slots[1], +#endif +#if defined(PATCHED) && defined(WHICH_PRIV) + .priv = &priv_slots[3], +#else + .priv = &priv_slots[1], +#endif +}; + +int target(int x) +{ + return descriptor.fn(x) + plain + sized[0] + (int)descriptor.str[0] + + *descriptor.slot + *descriptor.priv; +} diff --git a/tools/objtool/tests/generic/test-checksum-data.sh b/tools/objtool/tests/generic/test-checksum-data.sh new file mode 100755 index 000000000000..e915026b79a7 --- /dev/null +++ b/tools/objtool/tests/generic/test-checksum-data.sh @@ -0,0 +1,61 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# What a data object's checksum has to cover. +# +# checksum_update_object() hashes the symbol's length, its bytes (when the +# section has any -- .bss does not), and then +# every relocation it carries -- as the target's name plus the adjusted addend, +# except for a reference into a string section, which contributes the string's +# contents instead. +# +# Each of those is load-bearing, and the failure is always the same shape: a +# checksum that ignores one of them calls a changed object unchanged, klp diff +# leaves it out of the patch, and the patched code goes on reading the +# kernel's old copy. Nothing says so at build time. +# +# The string case is the one that cannot be caught by hashing bytes alone. The +# pointer is identical -- same section, same offset -- and only the text it +# refers to moved. +# +# Covers the same ground as corpus/x86_64/checksum-data-basic, +# checksum-data-func-ptr, checksum-data-string-ptr and checksum-string-reloc in +# Joe Lawrence's klp-build unit test corpus. + +. "$(dirname "$0")/../lib.sh" + +setup + +# check <flag> <symbol> <what changed> +# +# Build the pair with one difference and require that symbol's checksum to move. +check() +{ + build_pair checksum_data.c "-D$1" + run_checksum + + assert_checksum_differs "$2" +} + +# The object's own bytes. +check PLAIN_VALUE plain +# Its length, for a .bss object whose bytes are not hashed at all. +check LONGER sized +# A relocation's target: same bytes in the object, different symbol named. +check WHICH_FUNC descriptor +check WHICH_STR descriptor +# The contents of a string the object points at, with the pointer untouched. +check STR_CONTENT descriptor +# A relocation's addend: same target symbol, different offset into it. +check WHICH_SLOT descriptor +# The same, for a static reached through its section symbol: the reference has +# to be resolved back to the object before there is a name or offset to hash. +check WHICH_PRIV descriptor + +# Having shown five things that must change it, show one that must not: an +# unrelated edit elsewhere in the file leaves this object alone. +build_pair checksum_data.c -DPLAIN_VALUE +run_checksum +assert_checksum_matches descriptor + +pass "data checksums cover length, bytes, reloc targets and string contents" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 47/58] objtool/klp: Add test for symbols with no checksum entry of their own 2026-09-11 18:49 ` [PATCH 26/58] objtool/klp: Add test for unchecksummed input Song Liu ` (19 preceding siblings ...) 2026-09-11 18:50 ` [PATCH 46/58] objtool/klp: Add test for data object checksums Song Liu @ 2026-09-11 18:50 ` Song Liu 2026-09-11 19:23 ` sashiko-bot 2026-09-11 18:50 ` [PATCH 48/58] objtool/klp: Add test for a static branch introduced by the patch Song Liu ` (10 subsequent siblings) 31 siblings, 1 reply; 77+ messages in thread From: Song Liu @ 2026-09-11 18:50 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu .discard.sym_checksum is an array of { address, checksum } looked up by the address a relocation points at, so the invariant is one entry per address. calculate_checksums() skips zero-length symbols, aliases and cold parts to keep it, and nothing checked that it does. A duplicate entry is not a build failure. It makes the lookup ambiguous, and whichever checksum loses is never consulted again -- so a function whose code changed can be read as unchanged and dropped from the patch. The alias skip is verified the usual way: remove it and the test fails. The other two are not isolated, and the test says so rather than implying otherwise. A zero-length symbol is excluded by several of the guards at once -- its section has no data either -- so removing any one of them changes nothing observable; the assertion stands as a check on the behaviour, not on the line that produces it. The cold-part skip needs a compiler that splits functions and is not reached here at all. Which of an aliased pair keeps the entry falls out of symbol table order, so the test requires exactly one of the two rather than naming a winner -- as written first it named real_function and failed, because gcc kept the alias. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence <joe.lawrence@redhat.com> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../tests/generic/fixtures/checksum_skip.c | 47 ++++++++++++ .../tests/generic/test-checksum-skip.sh | 74 +++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/checksum_skip.c create mode 100755 tools/objtool/tests/generic/test-checksum-skip.sh diff --git a/tools/objtool/tests/generic/fixtures/checksum_skip.c b/tools/objtool/tests/generic/fixtures/checksum_skip.c new file mode 100644 index 000000000000..973bdc10295d --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/checksum_skip.c @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Symbols calculate_checksums() must not give an entry of their own. + * + * Three kinds are skipped, for two different reasons: + * + * zero-length there is nothing to hash, and an entry keyed on the + * symbol's address would collide with whatever really lives + * there. + * alias a second name for an address already checksummed. + * cold part hashed as part of its parent, which func_for_each_insn() + * walks into, so a separate entry would double-count it. + * + * An entry per address is the invariant: .discard.sym_checksum is looked up by + * the address a relocation points at, so two entries for one address make the + * lookup ambiguous and one of the two checksums unreachable. + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +/* Zero-length: an object symbol of size 0, in a section of its own. */ +extern char empty_marker[]; +__asm__(".pushsection .data.empty_marker,\"aw\",@progbits\n" + ".globl empty_marker\n" + ".type empty_marker, @object\n" + "empty_marker:\n" + ".size empty_marker, 0\n" + ".popsection\n"); + +int real_function(int x); +int real_function(int x) +{ +#ifdef PATCHED + return x + 2; +#else + return x + 1; +#endif +} + +/* Alias: a second name for real_function's address. */ +int alias_function(int x) __attribute__((alias("real_function"))); + +int target(int x) +{ + return real_function(x) + alias_function(x) + (int)(long)empty_marker; +} diff --git a/tools/objtool/tests/generic/test-checksum-skip.sh b/tools/objtool/tests/generic/test-checksum-skip.sh new file mode 100755 index 000000000000..a4ce1889db8f --- /dev/null +++ b/tools/objtool/tests/generic/test-checksum-skip.sh @@ -0,0 +1,74 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Symbols which must not get a checksum entry of their own. +# +# .discard.sym_checksum is an array of { address, checksum } looked up by the +# address a relocation points at, so the invariant is one entry per address. +# calculate_checksums() skips three kinds of symbol to keep it: +# +# zero-length nothing to hash, and its address belongs to whatever really +# lives there +# alias a second name for an address already covered +# cold part hashed into its parent, which func_for_each_insn() walks +# into, so its own entry would double-count +# +# A duplicate entry is not a build failure. It makes the lookup ambiguous, and +# whichever checksum loses is simply never consulted again -- so a function +# whose code changed can be read as unchanged and dropped from the patch. +# +# Of the three, only the alias skip is isolated here: removing it makes this +# test fail. A zero-length symbol is excluded by more than one of the guards +# at once -- its section has no data either -- so no single change makes that +# assertion fail, and it stands as a check on the behaviour rather than on the +# line which produces it. Nothing here reaches the cold-part skip, which +# wants a compiler that splits functions; test-cold-function covers that +# symbol surviving into the patch, not its checksum. +# +# Covers the same ground as corpus/x86_64/checksum-zero-len-sym, +# checksum-alias-skip and checksum-cold-skip in Joe Lawrence's klp-build unit +# test corpus. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair checksum_skip.c + +assert_input_symbol empty_marker +assert_input_symbol alias_function +run_checksum + +# entries_for <object> +# +# The symbol names .discard.sym_checksum has an entry for, one per line. +entries_for() +{ + in_relocs "$1" | awk '/rela\.discard\.sym_checksum/,/^$/ { + if ($1 ~ /^[0-9a-f]{8,}/) print $5 + }' +} + +entries="$(entries_for orig.o)" + +# The control: something real did get an entry, so an empty listing cannot +# make the rest of this pass by default. +echo "$entries" | grep -qx target || + fail "no checksum entry for target" + +echo "$entries" | grep -qx empty_marker && + fail "zero-length symbol got a checksum entry" + +# One of the two names for that address is kept and the other skipped; which +# one falls out of symbol table order and is not the point. Two would be. +n="$(echo "$entries" | grep -cxE 'real_function|alias_function')" +[ "$n" = 1 ] || + fail "expected 1 checksum entry across real_function and its alias, found $n" + +# One entry per address, which is what the skipping is for. +dupes="$(in_relocs orig.o | awk '/rela\.discard\.sym_checksum/,/^$/ { + if ($1 ~ /^[0-9a-f]{8,}/) print $1 + }' | sort | uniq -d)" +[ -z "$dupes" ] || + fail "two checksum entries for one address: $dupes" + +pass "zero-length symbols and aliases get no checksum entry of their own" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* Re: [PATCH 47/58] objtool/klp: Add test for symbols with no checksum entry of their own 2026-09-11 18:50 ` [PATCH 47/58] objtool/klp: Add test for symbols with no checksum entry of their own Song Liu @ 2026-09-11 19:23 ` sashiko-bot 0 siblings, 0 replies; 77+ messages in thread From: sashiko-bot @ 2026-09-11 19:23 UTC (permalink / raw) To: live-patching, Song Liu Cc: joe.lawrence, kernel-team, jikos, puranjay, pmladek, mbenes, peterz, jpoimboe Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] The test incorrectly checks for duplicate target addresses in `.discard.sym_checksum` by searching for duplicate relocation offsets (`$1`), causing the duplicate check to always silently pass. -- commit 1616f973328c423b8bf16ffbd15bb9fb6ff8d48a Author: Song Liu <song@kernel.org> objtool/klp: Add test for symbols with no checksum entry of their own This commit adds a test to ensure that symbols without their own checksum entries, like zero-length symbols or aliases, do not erroneously create duplicate entries in .discard.sym_checksum. > diff --git a/tools/objtool/tests/generic/test-checksum-skip.sh b/tools/objtool/tests/generic/test-checksum-skip.sh > new file mode 100755 > index 0000000000000..a4ce1889db8f0 > --- /dev/null > +++ b/tools/objtool/tests/generic/test-checksum-skip.sh [ ... ] > +# One entry per address, which is what the skipping is for. > +dupes="$(in_relocs orig.o | awk '/rela\.discard\.sym_checksum/,/^$/ { > + if ($1 ~ /^[0-9a-f]{8,}/) print $1 > + }' | sort | uniq -d)" > +[ -z "$dupes" ] || > + fail "two checksum entries for one address: $dupes" [Severity: Medium] Does this logic actually verify the "one entry per address" invariant? By extracting $1 (the relocation offset) instead of the target symbol or address, it appears this code only checks for duplicate offsets within the section. Since relocation offsets are inherently unique within a section, does this mean the duplicate check will silently pass even if multiple checksum entries point to the exact same address? -- Sashiko AI review · https://sashiko.dev/#/patchset/20260911185031.1534046-1-song@kernel.org?part=47 ^ permalink raw reply [flat|nested] 77+ messages in thread
* [PATCH 48/58] objtool/klp: Add test for a static branch introduced by the patch 2026-09-11 18:49 ` [PATCH 26/58] objtool/klp: Add test for unchecksummed input Song Liu ` (20 preceding siblings ...) 2026-09-11 18:50 ` [PATCH 47/58] objtool/klp: Add test for symbols with no checksum entry of their own Song Liu @ 2026-09-11 18:50 ` Song Liu 2026-09-11 18:50 ` [PATCH 49/58] objtool/klp: Add test for tracepoint and pr_debug static branch keys Song Liu ` (9 subsequent siblings) 31 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:50 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu Patching a function which already has a static branch and adding one to a function which had none are different cases. In the second the __jump_table entry is itself new, so there is nothing in the original to correlate it against: klp diff has to carry the entry into the patch from scratch and reach the key the way it reaches any other vmlinux symbol. Dropping it is silent. The patched function keeps a static branch the kernel never patches, so it takes the same arm forever whatever the key is set to. Give the fixture a NEW_KEY knob which puts the whole branch behind PATCHED, and assert the premise -- that the original really has no __jump_table -- before asserting the result, since otherwise this is just a second copy of test-jump-label-key. Verified by making klp diff skip __jump_table when cloning special sections: the test fails. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence <joe.lawrence@redhat.com> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../tests/generic/test-jump-label-new-key.sh | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100755 tools/objtool/tests/generic/test-jump-label-new-key.sh diff --git a/tools/objtool/tests/generic/test-jump-label-new-key.sh b/tools/objtool/tests/generic/test-jump-label-new-key.sh new file mode 100755 index 000000000000..3bc6005fc776 --- /dev/null +++ b/tools/objtool/tests/generic/test-jump-label-new-key.sh @@ -0,0 +1,51 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# A patch may introduce a static branch where the original function had none. +# +# That is not the same case as patching a function which already has one. The +# __jump_table entry is itself new, so there is no counterpart in the original +# to correlate it against: klp diff has to carry the entry and the key into the +# patch from scratch, and the key has to be reached the way any other reference +# to a vmlinux symbol is. +# +# Get it wrong and the entry is dropped, leaving a static branch the kernel +# never patches -- the code takes the wrong arm forever, silently. +# +# Where the key lives still decides whether that is allowed, exactly as it does +# for a key the original already had: a module-owned one cannot be reached, so +# introducing one has to stop the build rather than emit an entry nothing will +# resolve. +# +# Covers the same ground as corpus/x86_64/static-branch-vmlinux-new and +# static-branch-module-new in Joe Lawrence's klp-build unit test corpus. + +. "$(dirname "$0")/../lib.sh" + +setup klp_test_key +build_pair jump_label.c -DNEW_KEY + +# The premise: the original really has no jump table, and the patched one does. +has_input_section orig.o __jump_table && + fail "fixture put a __jump_table in the original; nothing new to add" +has_input_section patched.o __jump_table || + probe_skip "compiler produced no __jump_table on this arch" + +run_diff + +assert_patched target +assert_section __jump_table +assert_reloc_sym __jump_table target + +# The same new branch, with the key owned by a module. Drop the vmlinux export +# first: while it is exported the key is reachable and being new changes +# nothing, which is what the first version of this got wrong. +export_syms +rm -f "$workdir/out.o" +build_pair jump_label.c -DNEW_KEY -DMODNAME='"klp_testmod"' +run_diff 255 +assert_diff_log 'unsupported static branch key klp_test_key' +[ -e "$workdir/out.o" ] && + fail "output object produced for a rejected input" + +pass "static branch introduced by the patch carried in, or rejected for a module key" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 49/58] objtool/klp: Add test for tracepoint and pr_debug static branch keys 2026-09-11 18:49 ` [PATCH 26/58] objtool/klp: Add test for unchecksummed input Song Liu ` (21 preceding siblings ...) 2026-09-11 18:50 ` [PATCH 48/58] objtool/klp: Add test for a static branch introduced by the patch Song Liu @ 2026-09-11 18:50 ` Song Liu 2026-09-11 18:50 ` [PATCH 50/58] objtool/klp: Add test for a static call introduced by the patch Song Liu ` (8 subsequent siblings) 31 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:50 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu A module-owned static branch key is normally fatal, because late module patching lets the livepatch load before the module it depends on and jump_label_add_module() then dereferences an unresolved entry. Tracepoints and pr_debug() generate such keys everywhere, so refusing them outright would make any function containing a trace_*() call or a pr_debug() unpatchable. klp diff drops the entry, warns, and carries on: the patched code works with that one tracepoint or debug print permanently off. Both halves matter, and the test asserts both. A build which fails is a function nobody can patch; an entry left in place is the corruption the rejection exists to prevent. Give the fixture a KEY_NAME knob so the same static branch can be built with either special name. Verified by removing each exemption in turn -- the test fails for both. That the entry is then dropped is asserted but not isolated, and the test says so. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence <joe.lawrence@redhat.com> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../generic/test-jump-label-exempt-keys.sh | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100755 tools/objtool/tests/generic/test-jump-label-exempt-keys.sh diff --git a/tools/objtool/tests/generic/test-jump-label-exempt-keys.sh b/tools/objtool/tests/generic/test-jump-label-exempt-keys.sh new file mode 100755 index 000000000000..fa2f913d860b --- /dev/null +++ b/tools/objtool/tests/generic/test-jump-label-exempt-keys.sh @@ -0,0 +1,51 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Two kinds of module-owned static branch key are disabled with a warning +# instead of rejected. +# +# A module-local key is normally fatal: late module patching lets the livepatch +# load before the module it depends on, so the unresolved __jump_table entry is +# dereferenced by jump_label_add_module(). test-jump-label-module-key covers +# that rejection. +# +# Tracepoints and pr_debug() generate such keys everywhere, though, and +# refusing them outright would make any function containing a trace_*() call or +# a pr_debug() unpatchable. So klp diff drops the entry, says so, and carries +# on: the patched code keeps working with that one tracepoint or debug print +# permanently off. +# +# Both halves matter. A build that fails is a function nobody can patch; an +# entry left in place is the memory corruption the rejection exists to prevent. +# +# The two exemptions are isolated: remove either and this fails. That the +# entry is then dropped is asserted but not isolated -- making the caller keep +# it anyway produces no output difference here, so that assertion stands as a +# check on the behaviour rather than on the line which produces it. +# +# Covers the same ground as corpus/x86_64/static-call-module-tracepoint and +# pr-debug-unsupported in Joe Lawrence's klp-build unit test corpus. + +. "$(dirname "$0")/../lib.sh" + +setup + +# check <key name> <expected warning> +check() +{ + build_pair jump_label.c -DKEY_NAME="$1" -DMODNAME='"klp_testmod"' + require_input_section __jump_table + + # Accepted, not rejected: this is the whole point. + run_diff + assert_diff_log "$2" + + # And the entry is gone, not merely complained about. + assert_patched target + assert_no_section __jump_table +} + +check __tracepoint_klp_test 'disabling unsupported tracepoint klp_test' +check __UNIQUE_ID_ddebug_klp_test 'disabling unsupported pr_debug' + +pass "tracepoint and pr_debug keys disabled with a warning, not rejected" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 50/58] objtool/klp: Add test for a static call introduced by the patch 2026-09-11 18:49 ` [PATCH 26/58] objtool/klp: Add test for unchecksummed input Song Liu ` (22 preceding siblings ...) 2026-09-11 18:50 ` [PATCH 49/58] objtool/klp: Add test for tracepoint and pr_debug static branch keys Song Liu @ 2026-09-11 18:50 ` Song Liu 2026-09-11 19:25 ` sashiko-bot 2026-09-11 18:50 ` [PATCH 51/58] objtool/klp: Add test for instruction operand checksums Song Liu ` (7 subsequent siblings) 31 siblings, 1 reply; 77+ messages in thread From: Song Liu @ 2026-09-11 18:50 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu The counterpart to the static branch case: a patch may add a static call to a function which had none, so the .static_call_sites entry is new and there is nothing in the original to correlate it against. Where the key lives still decides whether that is allowed. A vmlinux key is reachable; a module-owned one is not, for the same reason an existing module key is not -- late module patching lets the livepatch load first and the unresolved entry is dereferenced when the module arrives. Both halves are here because they fail in opposite directions: dropping the new entry leaves a static call the kernel never patches, and accepting a new module-owned one is the corruption the check exists to prevent. Extend test-jump-label-new-key with the module half of the same question. That needed the vmlinux export dropped first -- while the key is exported it is reachable and being new changes nothing, so the first version of that addition passed for the wrong reason. klp-build unit test corpus. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence <joe.lawrence@redhat.com> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../tests/generic/test-static-call-new.sh | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100755 tools/objtool/tests/generic/test-static-call-new.sh diff --git a/tools/objtool/tests/generic/test-static-call-new.sh b/tools/objtool/tests/generic/test-static-call-new.sh new file mode 100755 index 000000000000..f7d2b39c0fec --- /dev/null +++ b/tools/objtool/tests/generic/test-static-call-new.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# A patch may introduce a static call where the original function had none. +# +# The .static_call_sites entry is then new, with nothing in the original to +# correlate it against, so klp diff has to carry it into the patch from +# scratch. Where the key lives still decides whether that is allowed: a +# vmlinux key is reachable, and a module-owned one is not, for the same reason +# an existing module key is not -- late module patching lets the livepatch load +# first, and the unresolved entry is dereferenced when the module arrives. +# +# Both halves are here because they fail in opposite directions. Dropping the +# new entry leaves a static call the kernel never patches; accepting a new +# module-owned one is the corruption the check exists to prevent. +# +# Covers the same ground as corpus/x86_64/static-call-vmlinux-new and +# static-call-module-new in Joe Lawrence's klp-build unit test corpus. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair static_call.c -DNEW_CALL + +# The premise: the original really has no static call, the patched one does. +has_input_section orig.o .static_call_sites && + fail "fixture put a .static_call_sites in the original; nothing new to add" +has_input_section patched.o .static_call_sites || + probe_skip "compiler produced no .static_call_sites on this arch" + +run_diff +assert_patched target +assert_section .static_call_sites +assert_reloc_sym .static_call_sites target + +# The same new call, with the key owned by a module: not reachable, so the +# build has to stop rather than emit a relocation nothing will resolve. +rm -f "$workdir/out.o" +build_pair static_call.c -DNEW_CALL -DMODNAME='"klp_testmod"' +run_diff 255 +assert_diff_log 'unsupported static call key __SCK__klp_test_call' +[ -e "$workdir/out.o" ] && + fail "output object produced for a rejected input" + +pass "static call introduced by the patch carried in, or rejected for a module key" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* Re: [PATCH 50/58] objtool/klp: Add test for a static call introduced by the patch 2026-09-11 18:50 ` [PATCH 50/58] objtool/klp: Add test for a static call introduced by the patch Song Liu @ 2026-09-11 19:25 ` sashiko-bot 0 siblings, 0 replies; 77+ messages in thread From: sashiko-bot @ 2026-09-11 19:25 UTC (permalink / raw) To: Song Liu, live-patching Cc: joe.lawrence, jpoimboe, mbenes, kernel-team, pmladek, jikos, peterz, puranjay Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] The commit message inaccurately describes the patch by claiming it modifies `test-jump-label-new-key` and drops a vmlinux export, which does not match the actual patch content. -- commit b26ef30fbd576efdbe08e0ea26b0bf01b92ab1b4 Author: Song Liu <song@kernel.org> objtool/klp: Add test for a static call introduced by the patch This commit adds a new test script to cover a case where a patch introduces a static call to a function which previously had none. It verifies that a new vmlinux key is accepted while a new module-owned key is rejected. > Extend test-jump-label-new-key with the module half of the same question. > That needed the vmlinux export dropped first -- while the key is exported > it is reachable and being new changes nothing, so the first version of that > addition passed for the wrong reason. klp-build unit test corpus. [Severity: Medium] Does this paragraph belong in the commit message for this patch? The text describes modifications to test-jump-label-new-key and dropping a vmlinux export, but the actual patch consists entirely of adding the new file tools/objtool/tests/generic/test-static-call-new.sh. The description also ends abruptly with "klp-build unit test corpus." Could the commit message be updated to match the contents of the patch? -- Sashiko AI review · https://sashiko.dev/#/patchset/20260911185031.1534046-1-song@kernel.org?part=50 ^ permalink raw reply [flat|nested] 77+ messages in thread
* [PATCH 51/58] objtool/klp: Add test for instruction operand checksums 2026-09-11 18:49 ` [PATCH 26/58] objtool/klp: Add test for unchecksummed input Song Liu ` (23 preceding siblings ...) 2026-09-11 18:50 ` [PATCH 50/58] objtool/klp: Add test for a static call introduced by the patch Song Liu @ 2026-09-11 18:50 ` Song Liu 2026-09-11 18:50 ` [PATCH 52/58] objtool/klp: Add test for alternative replacement code in checksums Song Liu ` (6 subsequent siblings) 31 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:50 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu checksum_update_insn() hashes an instruction's bytes and then what any relocation on it refers to: a string section contributes the string's contents, anything else the target symbol's name and adjusted addend, with a reference to a static resolved through its section symbol first. None of that shows up in the bytes. A rel32 operand is zero in the object and supplied by the relocation, so calling a different function, editing a literal the code passes, or reading a different index of an array all leave the encoded instruction byte-identical. A checksum stopping at the bytes reports the function unchanged and the patch silently does not contain the fix. test-checksum-position is the other half: what must *not* change the checksum when a function merely moves. Each of the four is verified by sabotaging the line it covers. The static case needed a writer the compiler cannot see through -- without one it proves the array is never written, folds every read to zero, and emits no relocation at all, so the reference under test does not exist and the variant passes having compared two identical objects. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence <joe.lawrence@redhat.com> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../tests/generic/fixtures/checksum_insn.c | 78 +++++++++++++++++++ .../tests/generic/test-checksum-insn.sh | 49 ++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/checksum_insn.c create mode 100755 tools/objtool/tests/generic/test-checksum-insn.sh diff --git a/tools/objtool/tests/generic/fixtures/checksum_insn.c b/tools/objtool/tests/generic/fixtures/checksum_insn.c new file mode 100644 index 000000000000..10f70a74a976 --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/checksum_insn.c @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Instruction operands whose change must move a function's checksum even + * though the instruction bytes themselves do not. + * + * checksum_update_insn() hashes the raw bytes and then, when the instruction + * carries a relocation, what that relocation refers to: a string section + * contributes the string's contents, anything else the target symbol's name + * and the adjusted addend. A reference to a static arrives as a section + * symbol and has to be resolved back to the object first. + * + * The bytes are identical in every case below -- a rel32 operand is zero in + * the object and supplied by the relocation -- so a checksum that stopped at + * the bytes would call all of these unchanged. + * + * Each variant applies to the patched build only: + * + * WHICH_CALL calls a different function + * STR_CONTENT passes a literal whose text was edited + * WHICH_SLOT reads a different index of a global array: addend only + * WHICH_PRIV the same, for a static, reached through its section symbol + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +int callee_a(int x); +int callee_b(int x); +int sink(const char *s); + +int slots[4]; + +/* + * A file-local array, plus a writer the compiler cannot see through. Without + * one it can prove the array is never written, folds every read to zero, and + * emits no relocation at all -- so the reference this is here to exercise does + * not exist. + */ +static int priv_slots[4]; + +void set_priv(int i, int v); +void set_priv(int i, int v) +{ + priv_slots[i] = v; +} + +#if defined(PATCHED) && defined(STR_CONTENT) +#define MESSAGE "edited" +#else +#define MESSAGE "original" +#endif + +int target(int x) +{ + int r; + +#if defined(PATCHED) && defined(WHICH_CALL) + r = callee_b(x); +#else + r = callee_a(x); +#endif + + r += sink(MESSAGE); + +#if defined(PATCHED) && defined(WHICH_SLOT) + r += slots[2]; +#else + r += slots[1]; +#endif + +#if defined(PATCHED) && defined(WHICH_PRIV) + r += priv_slots[3]; +#else + r += priv_slots[1]; +#endif + + return r; +} diff --git a/tools/objtool/tests/generic/test-checksum-insn.sh b/tools/objtool/tests/generic/test-checksum-insn.sh new file mode 100755 index 000000000000..e1c04a1f518a --- /dev/null +++ b/tools/objtool/tests/generic/test-checksum-insn.sh @@ -0,0 +1,49 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# What a function's checksum has to cover beyond its instruction bytes. +# +# checksum_update_insn() hashes the raw bytes, and then what any relocation on +# the instruction refers to: a string section contributes the string's +# contents, anything else the target symbol's name and the adjusted addend, +# with a reference to a static resolved back through its section symbol first. +# +# None of these show up in the bytes. A rel32 operand is zero in the object +# and supplied by the relocation, so every change below leaves the encoded +# instruction byte-identical. A checksum stopping at the bytes reports the +# function unchanged, klp diff omits it, and the patch silently does not +# contain the fix. +# +# test-checksum-position is the other half of this: it covers what must *not* +# change the checksum when a function merely moves. +# +# Covers the same ground as corpus/x86_64/checksum-reloc-sym, +# checksum-pc-relative-addend, checksum-string-reloc and +# checksum-sec-sym-resolve in Joe Lawrence's klp-build unit test corpus. + +. "$(dirname "$0")/../lib.sh" + +setup + +# check <flag> <what it changes> +check() +{ + build_pair checksum_insn.c "-D$1" + run_checksum + + # The premise for all of them: the operand is a relocation, not bytes. + assert_checksum_differs target +} + +check WHICH_CALL # relocation target name +check STR_CONTENT # contents of a string the code passes +check WHICH_SLOT # addend, same target symbol +check WHICH_PRIV # addend via a static's section symbol + +# The converse: rebuilding identical source leaves it alone, so the above is +# not just "any rebuild moves the checksum". +build_pair checksum_insn.c +run_checksum +assert_checksum_matches target + +pass "instruction checksums cover reloc targets, addends and string contents" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 52/58] objtool/klp: Add test for alternative replacement code in checksums 2026-09-11 18:49 ` [PATCH 26/58] objtool/klp: Add test for unchecksummed input Song Liu ` (24 preceding siblings ...) 2026-09-11 18:50 ` [PATCH 51/58] objtool/klp: Add test for instruction operand checksums Song Liu @ 2026-09-11 18:50 ` Song Liu 2026-09-11 19:30 ` sashiko-bot 2026-09-11 18:50 ` [PATCH 53/58] objtool/klp: Add test for the alignment of cloned data sections Song Liu ` (5 subsequent siblings) 31 siblings, 1 reply; 77+ messages in thread From: Song Liu @ 2026-09-11 18:50 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu checksum_update_insn() walks insn->alts after hashing the instruction itself: the alternative's type, and where the replacement forms a group, its feature number and every instruction in it. A patch which edits only the replacement -- code that runs on some CPUs and not others -- still has to move the function's checksum. When it does not, klp diff calls the function unchanged and leaves it out. The patch ships the old replacement, and the bug is fixed only on machines whose CPU takes the other arm. Which machines those are depends on the feature bit, so it presents as a machine-specific bug rather than a missing patch. insn->alts is built by objtool's check pass, not by the compiler, so the pair goes through that first. --mcount is the action used: it is the cheapest one that does not also need --link. Two of the three paths are isolated. Skipping the alts walk and dropping the feature hash both make this fail, and the second of those only exists inside the alt_group branch, so reaching it proves the grouped path is taken. The alternative's type is hashed but not varied here -- the fixture emits one kind of alternative -- so that line is covered without being isolated, as is the in_alt recursion guard, which wants nested alternatives. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence <joe.lawrence@redhat.com> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../objtool/tests/x86/fixtures/checksum_alt.c | 60 +++++++++++++++++++ tools/objtool/tests/x86/test-checksum-alt.sh | 45 ++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 tools/objtool/tests/x86/fixtures/checksum_alt.c create mode 100755 tools/objtool/tests/x86/test-checksum-alt.sh diff --git a/tools/objtool/tests/x86/fixtures/checksum_alt.c b/tools/objtool/tests/x86/fixtures/checksum_alt.c new file mode 100644 index 000000000000..47ff93bc7755 --- /dev/null +++ b/tools/objtool/tests/x86/fixtures/checksum_alt.c @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * An x86 alternative whose replacement code is part of the patched function's + * checksum. + * + * checksum_update_insn() walks insn->alts after hashing the instruction + * itself, hashing the alternative's type and, when the replacement forms a + * group, its feature number and every instruction in it. So editing only the + * replacement -- code the CPU may or may not ever run -- has to move the + * function's checksum. + * + * It is reached through objtool's own alternative handling, so the object has + * to go through the check pass first: insn->alts is built there, not by the + * compiler. + * + * struct alt_instr is written out by hand as in empty_alternative.c: s32 + * instr_offset, s32 repl_offset, u32 ft_flags, u8 instrlen, u8 replacementlen. + * + * Variants, applied to the patched build only: + * + * ALT_REPL the replacement instruction changes; the original does not + * ALT_FEATURE the feature number changes; no code changes at all + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +#if defined(PATCHED) && defined(ALT_REPL) +#define REPL_INSN " xchg %ax, %ax\n\t" +#else +#define REPL_INSN " nop\n\t" +#endif + +#if defined(PATCHED) && defined(ALT_FEATURE) +#define FEATURE "7" +#else +#define FEATURE "3" +#endif + +int target(int x) +{ + asm volatile( + "661: nop\n\t" + "662:\n\t" + ".pushsection .altinstr_replacement, \"ax\"\n\t" + ".globl target_repl\n\t" + "target_repl:\n\t" + REPL_INSN + "target_repl_end:\n\t" + ".popsection\n\t" + ".pushsection .altinstructions, \"aM\", @progbits, 14\n\t" + ".long 661b - .\n\t" + ".long target_repl - .\n\t" + ".long " FEATURE "\n\t" + ".byte 662b - 661b\n\t" + ".byte target_repl_end - target_repl\n\t" + ".popsection\n\t"); + + return x + 1; +} diff --git a/tools/objtool/tests/x86/test-checksum-alt.sh b/tools/objtool/tests/x86/test-checksum-alt.sh new file mode 100755 index 000000000000..74ebfca2b3ff --- /dev/null +++ b/tools/objtool/tests/x86/test-checksum-alt.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# An alternative's replacement code counts towards the checksum of the function +# it belongs to. +# +# checksum_update_insn() walks insn->alts after hashing the instruction itself: +# the alternative's type, and where the replacement forms a group, its feature +# number and every instruction in it. So a patch which edits only the +# replacement -- code that runs on some CPUs and not others -- still has to +# move the function's checksum. +# +# If it does not, klp diff decides the function is unchanged and leaves it out. +# The patch then ships the old replacement, and the bug is fixed only on +# machines whose CPU takes the other arm. Which machines those are depends on +# the feature bit, so the failure looks like a machine-specific bug rather than +# a missing patch. +# +# insn->alts exists only after objtool's check pass, so the pair goes through +# that first -- the compiler emits none of this structure itself. +# +# Covers the same ground as corpus/x86_64/checksum-alt-group, +# checksum-alt-no-group and checksum-alt-recursion-guard in Joe Lawrence's +# klp-build unit test corpus. + +. "$(dirname "$0")/../lib.sh" + +setup + +check() +{ + build_pair checksum_alt.c "-D$1" + assert_input_section .altinstructions + run_objtool_check --mcount + run_checksum + + assert_checksum_differs target +} + +# The replacement instruction itself. +check ALT_REPL +# The feature number, with no instruction anywhere changed. +check ALT_FEATURE + +pass "alternative replacement code counts towards the checksum" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* Re: [PATCH 52/58] objtool/klp: Add test for alternative replacement code in checksums 2026-09-11 18:50 ` [PATCH 52/58] objtool/klp: Add test for alternative replacement code in checksums Song Liu @ 2026-09-11 19:30 ` sashiko-bot 0 siblings, 0 replies; 77+ messages in thread From: sashiko-bot @ 2026-09-11 19:30 UTC (permalink / raw) To: live-patching, Song Liu Cc: kernel-team, jikos, peterz, joe.lawrence, jpoimboe, puranjay, mbenes, pmladek Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] The test fixture defines a replacement instruction that is larger than the original instruction, violating x86 alternative constraints. -- commit 509cbfff90fd6a32748a4ce13dc568b4181b4904 Author: Song Liu <song@kernel.org> objtool/klp: Add test for alternative replacement code in checksums checksum_update_insn() walks insn->alts after hashing the instruction itself: the alternative's type, and where the replacement forms a group, its feature number and every instruction in it. A patch which edits only the replacement -- code that runs on some CPUs and not others -- still has to move the function's checksum. > diff --git a/tools/objtool/tests/x86/fixtures/checksum_alt.c b/tools/objtool/tests/x86/fixtures/checksum_alt.c > new file mode 100644 > index 0000000000000..47ff93bc77556 > --- /dev/null > +++ b/tools/objtool/tests/x86/fixtures/checksum_alt.c > @@ -0,0 +1,60 @@ [ ... ] > +#if defined(PATCHED) && defined(ALT_REPL) > +#define REPL_INSN " xchg %ax, %ax\n\t" > +#else > +#define REPL_INSN " nop\n\t" > +#endif [ ... ] > +int target(int x) > +{ > + asm volatile( > + "661: nop\n\t" > + "662:\n\t" > + ".pushsection .altinstr_replacement, \"ax\"\n\t" > + ".globl target_repl\n\t" > + "target_repl:\n\t" > + REPL_INSN [Severity: Low] Since the original instruction defined at label 661 in target() is a 1-byte nop, and REPL_INSN can be defined as xchg %ax, %ax (which is 2 bytes), does this replacement exceed the original instruction's size? x86 alternatives strictly require the replacement instruction length to be less than or equal to the original instruction length. While this test fixture in checksum_alt.c does not execute at runtime, emitting structurally invalid metadata into the object file could cause failures if objtool adds stricter size validation in the future. > + "target_repl_end:\n\t" > + ".popsection\n\t" -- Sashiko AI review · https://sashiko.dev/#/patchset/20260911185031.1534046-1-song@kernel.org?part=52 ^ permalink raw reply [flat|nested] 77+ messages in thread
* [PATCH 53/58] objtool/klp: Add test for the alignment of cloned data sections 2026-09-11 18:49 ` [PATCH 26/58] objtool/klp: Add test for unchecksummed input Song Liu ` (25 preceding siblings ...) 2026-09-11 18:50 ` [PATCH 52/58] objtool/klp: Add test for alternative replacement code in checksums Song Liu @ 2026-09-11 18:50 ` Song Liu 2026-09-11 18:50 ` [PATCH 54/58] objtool/klp: Add test for a patch which strips a data annotation Song Liu ` (4 subsequent siblings) 31 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:50 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu A cloned data section has to keep its sh_addralign. Plenty of kernel data is aligned for correctness rather than speed -- per-CPU variables, anything touched by an aligned vector move, structures padded to own a cacheline -- and a clone that lands under-aligned either faults on first use or silently shares a line it was laid out to avoid. Neither shows up until the patch is loaded on hardware that cares. The fixture's data is new in the patched build, so klp diff has to clone it rather than reference the kernel's copy, and it asserts that premise before asserting the result. Commit 2f2600decb30 ("objtool/klp: fix data alignment in __clone_symbol()") cannot be reverted to check this -- the revert is a no-op against the current code, which has been rewritten since. Verified instead by forcing the clone's alignment to 1, which the test reports as "alignment 1, expected 64". Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence <joe.lawrence@redhat.com> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../tests/generic/fixtures/data_alignment.c | 29 ++++++++++++++ .../tests/generic/test-data-alignment.sh | 40 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/data_alignment.c create mode 100755 tools/objtool/tests/generic/test-data-alignment.sh diff --git a/tools/objtool/tests/generic/fixtures/data_alignment.c b/tools/objtool/tests/generic/fixtures/data_alignment.c new file mode 100644 index 000000000000..900253dfb2cb --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/data_alignment.c @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Data with an alignment stricter than its size. + * + * A cloned data section has to keep its sh_addralign. The kernel has plenty + * of data whose alignment is a correctness property rather than an + * optimisation -- per-CPU variables, anything touched by an aligned SSE move, + * cacheline-aligned locks -- and a clone that lands under-aligned faults or + * silently shares a cacheline it was written to avoid. + * + * The object is new in the patched build, so klp diff has to clone it rather + * than reference the kernel's copy. + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +#ifdef PATCHED +int aligned_data[2] __attribute__((aligned(64))) = { 1, 2 }; +#endif + +int target(int x) +{ +#ifdef PATCHED + return x + aligned_data[0]; +#else + return x; +#endif +} diff --git a/tools/objtool/tests/generic/test-data-alignment.sh b/tools/objtool/tests/generic/test-data-alignment.sh new file mode 100755 index 000000000000..8e389544a3b1 --- /dev/null +++ b/tools/objtool/tests/generic/test-data-alignment.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# A cloned data section keeps its alignment. +# +# Plenty of kernel data is aligned for correctness rather than speed: per-CPU +# variables, anything touched by an aligned vector move, structures padded to +# own a cacheline. A clone that lands under-aligned either faults on first use +# or silently shares a line it was laid out to avoid, and neither shows up +# until the patch is loaded on hardware that cares. +# +# Fixed by 2f2600decb30 ("objtool/klp: Fix alignment of cloned data +# sections"). +# +# Covers the same ground as corpus/x86_64/cloned-data-alignment in Joe +# Lawrence's klp-build unit test corpus. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair data_alignment.c + +# The premise: the compiler really did over-align it, and the object is new in +# the patch so it has to be cloned rather than referenced. +want="$(in_sections patched.o | sed 's/^ *\[[ 0-9]*\] *//' | + awk '$1 == ".data.aligned_data" { print $NF }')" +[ "$want" = 64 ] || + probe_skip "compiler gave .data.aligned_data alignment '$want', not 64" +has_input_section orig.o .data.aligned_data && + fail "fixture put aligned_data in the original; nothing to clone" + +run_diff +assert_section .data.aligned_data + +got="$(out_sections | sed 's/^ *\[[ 0-9]*\] *//' | + awk '$1 == ".data.aligned_data" { print $NF }')" +[ "$got" = "$want" ] || + fail "cloned .data.aligned_data has alignment $got, expected $want" + +pass "cloned data section keeps its alignment" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 54/58] objtool/klp: Add test for a patch which strips a data annotation 2026-09-11 18:49 ` [PATCH 26/58] objtool/klp: Add test for unchecksummed input Song Liu ` (26 preceding siblings ...) 2026-09-11 18:50 ` [PATCH 53/58] objtool/klp: Add test for the alignment of cloned data sections Song Liu @ 2026-09-11 18:50 ` Song Liu 2026-09-11 19:24 ` sashiko-bot 2026-09-11 18:50 ` [PATCH 55/58] objtool/klp: Add test for absolute and __ADDRESSABLE symbols Song Liu ` (3 subsequent siblings) 31 siblings, 1 reply; 77+ messages in thread From: Song Liu @ 2026-09-11 18:50 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu klp diff needs entry boundaries for a special section: either an entsize, or ANNOTATE_DATA_SPECIAL annotations naming where each entry starts. .static_call_sites has no entsize, so the annotations are all there is -- and a patch can remove the last one in a translation unit while leaving the section itself in place, so that only the patched side has lost them. The section still has to be handled. Dropping it leaves the patched function's static call unregistered; misreading its boundaries attaches the entry to the wrong code. Neither is reported at build time. Give the fixture a NO_ANNOTATE knob and assert the premise -- annotation present in the original, absent in the patched object, section present in both -- before asserting the result. Fixed by commit 3de711fba73a ("objtool/klp: Fix create_fake_symbols() skipping entsize-based sections"). Verified by making klp diff skip .static_call_sites when cloning special sections: the test fails. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence <joe.lawrence@redhat.com> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../test-static-call-annotate-stripped.sh | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100755 tools/objtool/tests/generic/test-static-call-annotate-stripped.sh diff --git a/tools/objtool/tests/generic/test-static-call-annotate-stripped.sh b/tools/objtool/tests/generic/test-static-call-annotate-stripped.sh new file mode 100755 index 000000000000..bdbc0a21c669 --- /dev/null +++ b/tools/objtool/tests/generic/test-static-call-annotate-stripped.sh @@ -0,0 +1,41 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# A patch may remove the last ANNOTATE_DATA_SPECIAL in a translation unit while +# leaving the special section it described in place. +# +# klp diff needs entry boundaries for a special section: either an entsize, or +# annotations naming where each entry starts. .static_call_sites has no +# entsize, so the annotations are all there is -- and when the patched object +# is the only side that lost them, the two sides no longer agree on how the +# section divides up. +# +# The section must still be handled. Dropping it would leave the patched +# function's static call unregistered; misreading its boundaries would attach +# the entry to the wrong code. Either way nothing is reported at build time. +# +# Fixed by 5c134aadb0d9. +# +# Covers the same ground as corpus/x86_64/static-call-annotate-stripped in Joe +# Lawrence's klp-build unit test corpus. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair static_call.c -DNO_ANNOTATE + +# The premise: the original describes its entry, the patched one no longer +# does, and both still have the section itself. +has_input_section orig.o .discard.annotate_data || + fail "fixture produced no annotation in the original" +has_input_section patched.o .discard.annotate_data && + fail "patched object still has the annotation; nothing was stripped" +assert_input_section .static_call_sites + +run_diff + +assert_patched target +assert_section .static_call_sites +assert_reloc_sym .static_call_sites target + +pass "static call site kept when the patch strips its data annotation" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* Re: [PATCH 54/58] objtool/klp: Add test for a patch which strips a data annotation 2026-09-11 18:50 ` [PATCH 54/58] objtool/klp: Add test for a patch which strips a data annotation Song Liu @ 2026-09-11 19:24 ` sashiko-bot 0 siblings, 0 replies; 77+ messages in thread From: sashiko-bot @ 2026-09-11 19:24 UTC (permalink / raw) To: Song Liu, live-patching Cc: joe.lawrence, mbenes, peterz, jikos, puranjay, kernel-team, jpoimboe, pmladek Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] The test script comment references a stale, non-existent commit hash instead of the correct one cited in the commit message. -- commit ebb1db72e2841f055fb71e4dbbeaab8dc57a8d05 Author: Song Liu <song@kernel.org> objtool/klp: Add test for a patch which strips a data annotation klp diff needs entry boundaries for a special section: either an entsize, or ANNOTATE_DATA_SPECIAL annotations naming where each entry starts. .static_call_sites has no entsize, so the annotations are all there is -- and a patch can remove the last one in a translation unit while leaving the section itself in place, so that only the patched side has lost them. > diff --git a/tools/objtool/tests/generic/test-static-call-annotate-stripped.sh b/tools/objtool/tests/generic/test-static-call-annotate-stripped.sh > new file mode 100755 > index 0000000000000..bdbc0a21c669e > --- /dev/null > +++ b/tools/objtool/tests/generic/test-static-call-annotate-stripped.sh > @@ -0,0 +1,41 @@ > +#!/bin/bash > +# SPDX-License-Identifier: GPL-2.0 > +# > +# A patch may remove the last ANNOTATE_DATA_SPECIAL in a translation unit while > +# leaving the special section it described in place. [ ... ] > +# The section must still be handled. Dropping it would leave the patched > +# function's static call unregistered; misreading its boundaries would attach > +# the entry to the wrong code. Either way nothing is reported at build time. > +# > +# Fixed by 5c134aadb0d9. [Severity: Medium] Does this comment reference a stale commit hash? The commit message correctly cites 3de711fba73a as the fixing commit, but the script comment cites 5c134aadb0d9. Should this comment be updated to match the commit message to ensure accurate historical traceability? > +# > +# Covers the same ground as corpus/x86_64/static-call-annotate-stripped in Joe > +# Lawrence's klp-build unit test corpus. -- Sashiko AI review · https://sashiko.dev/#/patchset/20260911185031.1534046-1-song@kernel.org?part=54 ^ permalink raw reply [flat|nested] 77+ messages in thread
* [PATCH 55/58] objtool/klp: Add test for absolute and __ADDRESSABLE symbols 2026-09-11 18:49 ` [PATCH 26/58] objtool/klp: Add test for unchecksummed input Song Liu ` (27 preceding siblings ...) 2026-09-11 18:50 ` [PATCH 54/58] objtool/klp: Add test for a patch which strips a data annotation Song Liu @ 2026-09-11 18:50 ` Song Liu 2026-09-11 18:50 ` [PATCH 56/58] objtool/klp: Add test for UBSAN metadata in an unchanged function Song Liu ` (2 subsequent siblings) 31 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:50 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu A SHN_ABS symbol has no section, so any walk of sym->sec which does not check dereferences NULL, and the kernel has plenty of them -- from linker scripts and from .set in assembly. __ADDRESSABLE() emits a pointer into .discard.addressable purely to keep a symbol referenced; it means nothing to a livepatch and is discarded at link time, but it is a relocation like any other and gets looked at. Neither is what the patch changes. What this guards against is not a wrong answer but a crash or an error on input the kernel produces routinely, which would make every function near one unpatchable. Not isolated to a single line, and the test says so: the absolute symbol here has zero length, so it is excluded before the section check is reached and removing that check alone changes nothing observable. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence <joe.lawrence@redhat.com> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../generic/fixtures/abs_and_addressable.c | 44 ++++++++++++++++ .../tests/generic/test-abs-and-addressable.sh | 50 +++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/abs_and_addressable.c create mode 100755 tools/objtool/tests/generic/test-abs-and-addressable.sh diff --git a/tools/objtool/tests/generic/fixtures/abs_and_addressable.c b/tools/objtool/tests/generic/fixtures/abs_and_addressable.c new file mode 100644 index 000000000000..6392ff99af42 --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/abs_and_addressable.c @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Two constructs which appear all over the kernel and must not upset klp + * checksum or klp diff. + * + * An absolute symbol (SHN_ABS) has no section, so anything walking sym->sec + * without checking dereferences NULL. The kernel makes them with linker + * scripts and with .set in asm; VDSO and the fixed-address per-cpu bases are + * the usual sources. + * + * __ADDRESSABLE() emits a pointer into .discard.addressable purely to keep a + * symbol referenced. It is discarded at link time and means nothing to a + * livepatch, but the pointer is a relocation like any other and has to survive + * being looked at. + * + * Neither is the subject of the patch; the point is that their presence does + * not disturb the function that is. + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +/* SHN_ABS, referenced from code. */ +extern char abs_sym[]; +__asm__(".globl abs_sym\n" + ".set abs_sym, 0x1234\n"); + +int helper(int x); +int helper(int x) { return x + 1; } + +/* The shape of __ADDRESSABLE(helper). */ +__asm__(".pushsection .discard.addressable, \"aw\"\n" + ".balign 8\n" + ".quad helper\n" + ".popsection\n"); + +int target(int x) +{ +#ifdef PATCHED + return helper(x) + (int)(long)abs_sym + 1; +#else + return helper(x) + (int)(long)abs_sym; +#endif +} diff --git a/tools/objtool/tests/generic/test-abs-and-addressable.sh b/tools/objtool/tests/generic/test-abs-and-addressable.sh new file mode 100755 index 000000000000..6adb23ed4b88 --- /dev/null +++ b/tools/objtool/tests/generic/test-abs-and-addressable.sh @@ -0,0 +1,50 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# An absolute symbol and an __ADDRESSABLE() pointer must not disturb the +# function being patched. +# +# A SHN_ABS symbol has no section, so any walk of sym->sec which does not check +# dereferences NULL -- and the kernel has plenty, from linker scripts and from +# .set in assembly. __ADDRESSABLE() emits a pointer into .discard.addressable +# to keep a symbol referenced; it means nothing to a livepatch and is discarded +# at link time, but it is a relocation like any other and gets looked at. +# +# Neither is what the patch changes. The failure this guards against is not a +# wrong answer but a crash or an error on input the kernel produces routinely, +# which would make any function near one unpatchable. +# +# Not isolated to a single guard: the absolute symbol here has zero length, so +# it is excluded before the section check is reached and removing that check +# alone changes nothing observable. This stands as a check on the behaviour +# rather than on the line which produces it. +# +# Covers the same ground as corpus/x86_64/checksum-abs-sym-skip and +# addressable-symbols in Joe Lawrence's klp-build unit test corpus. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair abs_and_addressable.c + +# The premise: the fixture really did produce both. +in_symbols orig.o | grep -q 'ABS.*abs_sym' || + probe_skip "assembler did not make abs_sym absolute here" +assert_input_section .discard.addressable + +# Checksumming has to survive them, and still see the function that changed. +run_checksum +assert_checksum_differs target +assert_checksum_matches helper + +# So does the diff. +run_diff +assert_patched target +assert_not_patched helper + +# An absolute symbol has no address to record a checksum against, so it gets +# no entry -- the reference to it is what mattered, not the symbol itself. +in_relocs orig.o | awk '/rela\.discard\.sym_checksum/,/^$/' | grep -qw abs_sym && + fail "absolute symbol got a checksum entry" + +pass "absolute and __ADDRESSABLE symbols do not disturb the patched function" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 56/58] objtool/klp: Add test for UBSAN metadata in an unchanged function 2026-09-11 18:49 ` [PATCH 26/58] objtool/klp: Add test for unchecksummed input Song Liu ` (28 preceding siblings ...) 2026-09-11 18:50 ` [PATCH 55/58] objtool/klp: Add test for absolute and __ADDRESSABLE symbols Song Liu @ 2026-09-11 18:50 ` Song Liu 2026-09-11 18:50 ` [PATCH 57/58] objtool/klp: Add test for Clang switch jump tables Song Liu 2026-09-11 18:50 ` [PATCH 58/58] objtool/klp: Add test for ThinLTO symbols sharing a demangled name Song Liu 31 siblings, 0 replies; 77+ messages in thread From: Song Liu @ 2026-09-11 18:50 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu Every instrumented operation gets a per-callsite metadata object in an anonymous data section -- .data..Lubsan_data and .data..Lubsan_type from GCC, .data..L__unnamed_ from Clang -- whose names are compiler-generated and mean nothing across a rebuild. is_uncorrelated_section() exists so klp diff does not try to pair them up, and nothing tested it. The failure it prevents is a false positive, which is the direction this suite has least coverage of. Metadata belonging to a function nobody touched compares as different and drags that function into the patch. That is not a build failure: it is a larger livepatch than intended, pulling in dependencies with it, and every extra function is one more that can fail to correlate or to apply. The fixture is built with -fsanitize=shift, which both compilers instrument; neither emits a bounds check for an index it can prove in range. One function changes, the other is byte-identical and carries instrumentation of its own, and the test asserts the second is left alone. Verified by removing each rule from is_uncorrelated_section() in turn, which splits neatly by toolchain: dropping the .data..Lubsan rule fails the test under gcc, dropping .data..L__unnamed_ fails it under clang. One test, two code paths, each checked by the compiler that reaches it. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence <joe.lawrence@redhat.com> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../tests/generic/fixtures/ubsan_noise.c | 49 +++++++++++++++++++ .../objtool/tests/generic/test-ubsan-noise.sh | 48 ++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/ubsan_noise.c create mode 100755 tools/objtool/tests/generic/test-ubsan-noise.sh diff --git a/tools/objtool/tests/generic/fixtures/ubsan_noise.c b/tools/objtool/tests/generic/fixtures/ubsan_noise.c new file mode 100644 index 000000000000..bf5999254163 --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/ubsan_noise.c @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A translation unit built with UBSAN, where only one of two functions is + * patched. + * + * Every instrumented operation gets a per-callsite metadata object in an + * anonymous data section -- .data..Lubsan_data and .data..Lubsan_type from + * GCC, .data..L__unnamed_ from Clang -- and a call to a __ubsan_handle_* + * routine. The names are compiler-generated and carry no meaning across a + * rebuild, so klp diff has to treat those sections as uncorrelated rather than + * pairing them up by name. + * + * untouched() is byte-identical in both builds and exists to catch the false + * positive: if the metadata were correlated by name, its shifts would look + * changed and it would be dragged into the patch. + * + * The shifts are what draw the instrumentation. A bounds check would do as + * well but neither compiler emits one for an index it can prove in range. + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +int shift_by(int v, int n); + +int untouched(int v, int n) +{ + int s = 0; + + s += v << (n & 31); + s += v << ((n + 1) & 31); + s += shift_by(v, n); + + return s; +} + +int touched(int v, int n) +{ + int s = 0; + + s += v << (n & 31); +#ifdef PATCHED + s += v << ((n + 3) & 31); +#else + s += v << ((n + 2) & 31); +#endif + + return s; +} diff --git a/tools/objtool/tests/generic/test-ubsan-noise.sh b/tools/objtool/tests/generic/test-ubsan-noise.sh new file mode 100755 index 000000000000..b415eb16bfd2 --- /dev/null +++ b/tools/objtool/tests/generic/test-ubsan-noise.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# UBSAN instrumentation in an unchanged function must not make it look changed. +# +# Every instrumented operation gets a per-callsite metadata object in an +# anonymous data section -- .data..Lubsan_data and .data..Lubsan_type from GCC, +# .data..L__unnamed_ from Clang -- whose names are compiler-generated and mean +# nothing across a rebuild. is_uncorrelated_section() exists so klp diff does +# not try to pair them up. +# +# Without that, the metadata belonging to a function nobody touched compares as +# different and drags the function into the patch. A livepatch which replaces +# functions the patch never changed is not a build failure: it is a larger +# patch than intended, taking its dependencies with it, and every extra +# function is one more that can fail to correlate or to apply. +# +# Covers the same ground as corpus/x86_64-ubsan/{ubsan-shift-noise, +# ubsan-metadata-data-section,gcc-ubsan-anonymous-data,ubsan-handler-cloning} +# and corpus/x86_64-llvm-ubsan/{clang-ubsan-bounds-noise, +# clang-ubsan-handler-cloning} in Joe Lawrence's klp-build unit test corpus. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair ubsan_noise.c -fsanitize=shift + +# The premise: this compiler really did instrument, and left its metadata in an +# anonymous section. Without that the test is just test-basic again. +ubsan_sec="$(in_sections orig.o | + grep -oE '\.data\.\.L(ubsan_data|__unnamed_)[A-Za-z0-9_.]*' | head -1)" +[ -n "$ubsan_sec" ] || + probe_skip "compiler emitted no anonymous UBSAN data section" +assert_input_symbol untouched + +run_diff + +# The changed function is patched, and the untouched one is left alone despite +# carrying instrumentation of its own. +assert_patched touched +assert_not_patched untouched + +# The handler the patched code calls has to come with it, or the clone calls +# nothing when its check fires. +out_symbols | grep -q '__ubsan_handle_' || + fail "no __ubsan_handle_* reference in the patched output" + +pass "UBSAN metadata in an unchanged function does not drag it into the patch" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* [PATCH 57/58] objtool/klp: Add test for Clang switch jump tables 2026-09-11 18:49 ` [PATCH 26/58] objtool/klp: Add test for unchecksummed input Song Liu ` (29 preceding siblings ...) 2026-09-11 18:50 ` [PATCH 56/58] objtool/klp: Add test for UBSAN metadata in an unchanged function Song Liu @ 2026-09-11 18:50 ` Song Liu 2026-09-11 19:27 ` sashiko-bot 2026-09-11 18:50 ` [PATCH 58/58] objtool/klp: Add test for ThinLTO symbols sharing a demangled name Song Liu 31 siblings, 1 reply; 77+ messages in thread From: Song Liu @ 2026-09-11 18:50 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu For a dense enough switch Clang emits the targets as a table in .rodata..Lswitch.table.<function> -- named after the function but not part of it. The patched function indexes into that table, so a clone which does not bring it along jumps through whatever the kernel's copy holds, which after a patch that changed the switch is the wrong set of targets. An indirect jump to a stale address reports nothing at build or load time. The fixture asserts its own premise twice over, since both halves depend on what this Clang chose to do: that a table was built rather than a chain of comparisons, and that the added case actually changed it. objtool has no switch-specific code -- the table is carried by the general mechanism for data a cloned function references -- so this guards that mechanism reaching an easily-mishandled shape rather than a particular line, and the test says so. Making the table uncorrelated, the nearest available sabotage, does not change the outcome. Also fix the runner's leak detection, which this test found: a run in which every test skipped before creating a working directory left the "$rundir"/*/ glob unexpanded, so rmdir failed on a literal pattern and the run reported a leak that had not happened. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence <joe.lawrence@redhat.com> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../tests/generic/fixtures/switch_rodata.c | 31 +++++++++++ .../tests/generic/test-switch-rodata.sh | 53 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/switch_rodata.c create mode 100755 tools/objtool/tests/generic/test-switch-rodata.sh diff --git a/tools/objtool/tests/generic/fixtures/switch_rodata.c b/tools/objtool/tests/generic/fixtures/switch_rodata.c new file mode 100644 index 000000000000..817ddac92d81 --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/switch_rodata.c @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A switch dense enough that Clang builds a jump table for it, in a section of + * its own: .rodata..Lswitch.table.<function>. + * + * The table belongs to the function and has to travel with it. It is named + * after the function but is not part of it, so klp diff has to associate the + * two rather than treating the table as unrelated data. + * + * The patch adds a case, which changes the table's contents and length. + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; +const char *status_to_string(unsigned int c) +{ + switch (c) { + case 0: return "idle"; + case 1: return "running"; + case 2: return "stopped"; + case 3: return "error"; + case 4: return "paused"; + case 5: return "waiting"; + case 6: return "starting"; + case 7: return "stopping"; +#ifdef PATCHED + case 8: return "completed"; +#endif + } + return "unknown"; +} diff --git a/tools/objtool/tests/generic/test-switch-rodata.sh b/tools/objtool/tests/generic/test-switch-rodata.sh new file mode 100755 index 000000000000..fb27e96c65f6 --- /dev/null +++ b/tools/objtool/tests/generic/test-switch-rodata.sh @@ -0,0 +1,53 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# A Clang switch jump table travels with the function it belongs to. +# +# For a dense enough switch Clang emits the targets as a table in +# .rodata..Lswitch.table.<function>, named after the function but not part of +# it. klp diff has to associate the two: the patched function indexes into +# that table, so a clone which does not bring it along jumps through whatever +# the kernel's copy holds -- which, when the patch changed the switch, is the +# wrong set of targets. +# +# That is an indirect jump to a stale address, not a missing symbol, so nothing +# reports it at build or load time. +# +# objtool has no switch-specific code: the table is carried by the general +# mechanism for data a cloned function references. So this is a regression +# test on that mechanism reaching a shape it is easy to get wrong, not a guard +# on a particular line -- making the table uncorrelated, the nearest sabotage, +# does not change the outcome. +# +# Covers the same ground as corpus/x86_64-llvm-switch-rodata/ +# clang-switch-rodata-assoc in Joe Lawrence's klp-build unit test corpus. + +. "$(dirname "$0")/../lib.sh" + +clang_only "only Clang emits switch jump tables in their own section" + +setup +build_pair switch_rodata.c + +# The premise: this Clang really did build a table rather than a chain of +# comparisons, and the added case really did change it. +tbl=.rodata..Lswitch.table.status_to_string +has_input_section orig.o "$tbl" || + probe_skip "this clang built no jump table for the switch" +# readelf prefixes each line with "[nn]", which splits into one or two fields +# depending on the index, so strip it before counting columns. +tbl_size() +{ + in_sections "$1" | sed 's/^ *\[[ 0-9]*\] *//' | + awk -v s="$tbl" '$1 == s { print $5 }' +} +[ "$(tbl_size orig.o)" != "$(tbl_size patched.o)" ] || + fail "fixture's added case did not change the jump table" + +run_diff + +assert_patched status_to_string +assert_section "$tbl" +assert_reloc_sym .text.status_to_string "$tbl" + +pass "Clang switch jump table carried with the function it belongs to" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* Re: [PATCH 57/58] objtool/klp: Add test for Clang switch jump tables 2026-09-11 18:50 ` [PATCH 57/58] objtool/klp: Add test for Clang switch jump tables Song Liu @ 2026-09-11 19:27 ` sashiko-bot 0 siblings, 0 replies; 77+ messages in thread From: sashiko-bot @ 2026-09-11 19:27 UTC (permalink / raw) To: live-patching, Song Liu Cc: peterz, pmladek, puranjay, joe.lawrence, mbenes, jpoimboe, jikos, kernel-team Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] The commit message claims to fix a leak detection bug in the test runner, but the patch completely omits the fix. -- commit 5c4ab407591b68fa95b718465323f3522b8813cc Author: Song Liu <song@kernel.org> objtool/klp: Add test for Clang switch jump tables This commit adds a test to ensure objtool correctly handles Clang switch jump tables in their own section. It asserts that klp diff associates the patched function with its .rodata..Lswitch.table.<function> section. > Also fix the runner's leak detection, which this test found: a run in which > every test skipped before creating a working directory left the > "$rundir"/*/ glob unexpanded, so rmdir failed on a literal pattern and the > run reported a leak that had not happened. [Severity: Medium] The commit message claims to fix a leak detection bug in the test runner where the "$rundir"/*/ glob is left unexpanded when tests are skipped before creating a working directory. However, the patch does not contain any modifications to tools/objtool/tests/run-tests.sh to correct the rmdir command. Was the fix for the runner script inadvertently omitted from this commit? -- Sashiko AI review · https://sashiko.dev/#/patchset/20260911185031.1534046-1-song@kernel.org?part=57 ^ permalink raw reply [flat|nested] 77+ messages in thread
* [PATCH 58/58] objtool/klp: Add test for ThinLTO symbols sharing a demangled name 2026-09-11 18:49 ` [PATCH 26/58] objtool/klp: Add test for unchecksummed input Song Liu ` (30 preceding siblings ...) 2026-09-11 18:50 ` [PATCH 57/58] objtool/klp: Add test for Clang switch jump tables Song Liu @ 2026-09-11 18:50 ` Song Liu 2026-09-11 19:28 ` sashiko-bot 31 siblings, 1 reply; 77+ messages in thread From: Song Liu @ 2026-09-11 18:50 UTC (permalink / raw) To: live-patching Cc: jpoimboe, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team, Song Liu A file-local symbol which ThinLTO has to make visible is renamed helper.llvm.<hash>. With two such helpers in one link, the original and the patched object hold two each, all four spelled differently, and demangling gives "helper" for every one of them -- so the name alone cannot say which corresponds to which. Three translation units, two with a static helper of the same name and a third calling into both, which is what forces the promotion. Only one helper changes: paired correctly that means exactly one is cloned, and paired the wrong way round the other is, or both are. Had both bodies changed, both would be cloned either way and the test would prove nothing -- which is how the first version of this was written. The outcome is asserted, not the machinery. With the clang tested here the pairing survives disabling the .llvm.<hash> suffix map and stubbing out llvm_suffix() entirely, so no single-line sabotage distinguishes it; the tiered matcher this case was written for is not needed for this shape. The test says so rather than implying otherwise. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence <joe.lawrence@redhat.com> Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu <song@kernel.org> --- .../generic/fixtures/thinlto_ambiguity.c | 55 +++++++++++++ .../tests/generic/test-thinlto-ambiguity.sh | 77 +++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/thinlto_ambiguity.c create mode 100755 tools/objtool/tests/generic/test-thinlto-ambiguity.sh diff --git a/tools/objtool/tests/generic/fixtures/thinlto_ambiguity.c b/tools/objtool/tests/generic/fixtures/thinlto_ambiguity.c new file mode 100644 index 000000000000..4a87bc92ea2e --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/thinlto_ambiguity.c @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Three translation units linked with ThinLTO, two of which have a file-local + * helper of the same name. + * + * TU_C calls into both of the others, so ThinLTO imports entry_a and entry_b + * and with them the static helper each one calls. A file-local symbol which + * has to become visible is renamed helper.llvm.<hash>, and the hash is content + * derived -- so the two helpers get different hashes from each other, and + * different ones again after the patch changes them both. + * + * That leaves klp diff with two symbols in the original and two in the patched + * object, all four named differently, which have to be paired up correctly. + * Demangling alone gives "helper" for all of them; something else has to + * decide which is which. + * + * Only TU_A's helper changes. That is what makes a wrong pairing observable: + * paired correctly, one helper is changed and the other is not, so exactly one + * is cloned. Paired the wrong way round, both look changed -- or the wrong + * one does. If both bodies changed the outcome would be the same either way + * and the test would prove nothing. + * + * BASE differs between the two so their bodies are not identical to begin + * with. + */ + +#if defined(TU_C) +extern int entry_a(int x); +extern int entry_b(int x); +int glue(int x) { return entry_a(x) + entry_b(x + 1); } +#else +#ifdef TU_B +#define ENTRY entry_b +#define BASE 5 +#else +#define ENTRY entry_a +#define BASE 10 +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; +#endif +static __attribute__((noinline)) int helper(int x, int len) +{ + int sum = 0, i; + + for (i = 0; i < len; i++) +#if defined(PATCHED) && !defined(TU_B) + sum += i * 2 + BASE; /* only TU_A's helper changes */ +#else + sum += i + BASE; +#endif + return sum + x; +} + +int ENTRY(int x) { return helper(x, 4); } +#endif diff --git a/tools/objtool/tests/generic/test-thinlto-ambiguity.sh b/tools/objtool/tests/generic/test-thinlto-ambiguity.sh new file mode 100755 index 000000000000..34f4f3a58e20 --- /dev/null +++ b/tools/objtool/tests/generic/test-thinlto-ambiguity.sh @@ -0,0 +1,77 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Two ThinLTO-promoted symbols sharing a demangled name must be paired up +# correctly. +# +# A file-local symbol which ThinLTO has to make visible is renamed +# helper.llvm.<hash>. With two such helpers in one link the original and the +# patched object hold two each, all four spelled differently, and demangling +# gives "helper" for all of them -- so the name is not enough to say which +# corresponds to which. +# +# Getting it wrong is silent and specific: the patch is built against the wrong +# body, so one call site gets the other helper's arithmetic. Nothing fails to +# build and nothing fails to load. +# +# test-thinlto-local covers the unambiguous case, one promoted symbol whose +# hash moved. This is the case where demangling alone is not an answer. +# +# The outcome is asserted, not the machinery: with the clang tested here the +# pairing succeeds even with the .llvm.<hash> suffix map disabled and with +# llvm_suffix() stubbed out, so no single-line sabotage distinguishes it. The +# tiered matcher this case was written for is not needed for this shape. +# +# Covers the same ground as corpus/x86_64-llvm-thinlto/ +# thin-lto-demangled-ambiguity and thin-lto-demangled-global-match in Joe +# Lawrence's klp-build unit test corpus. + +. "$(dirname "$0")/../lib.sh" + +setup +clang_only "ThinLTO requires clang" + +find_thinlto_toolchain || + probe_skip "no matching clang/lld pair for a ThinLTO link; set THIN_LD to one" + +build_thinlto() # $1 output object, $2 extra flags +{ + local t + for t in "" -DTU_B -DTU_C; do + $THIN_CC -flto=thin -O2 -ffunction-sections -fdata-sections \ + $2 $t -c "$FIXTURES_DIR/thinlto_ambiguity.c" \ + -o "$workdir/tu$t.o" 2>/dev/null || return 1 + done + "$THIN_LD" -r "$workdir/tu.o" "$workdir/tu-DTU_B.o" "$workdir/tu-DTU_C.o" \ + -o "$1" 2>/dev/null || return 1 +} + +build_thinlto "$workdir/orig.o" "" || + probe_skip "ThinLTO build failed ($THIN_CC, $THIN_LD)" +build_thinlto "$workdir/patched.o" -DPATCHED || + probe_skip "ThinLTO build failed ($THIN_CC, $THIN_LD)" + +# The premise: two promoted helpers per object, and exactly one of them kept +# its hash -- the one the patch did not touch. Without that there is nothing +# to disambiguate. +orig_syms="$(in_symbols orig.o | grep -oE 'helper\.llvm\.[0-9]+' | sort -u)" +new_syms="$( in_symbols patched.o | grep -oE 'helper\.llvm\.[0-9]+' | sort -u)" +[ "$(echo "$orig_syms" | wc -l)" = 2 ] && [ "$(echo "$new_syms" | wc -l)" = 2 ] || + probe_skip "ThinLTO did not promote two distinct helpers here" + +kept="$(comm -12 <(echo "$orig_syms") <(echo "$new_syms"))" +moved="$(comm -13 <(echo "$orig_syms") <(echo "$new_syms"))" +[ "$(echo "$kept" | wc -w)" = 1 ] && [ "$(echo "$moved" | wc -w)" = 1 ] || + probe_skip "expected one helper to keep its hash and one to move" + +run_diff + +# Exactly one helper is cloned, and it is the one whose body changed. Cloning +# the other, or both, is what a wrong pairing looks like. +assert_not_patched "$kept" + +n="$(out_sections | grep -cE '[[:space:]]\.text\.helper\.llvm\.[0-9]+[[:space:]]')" +[ "$n" = 1 ] || + fail "expected 1 cloned helper, found $n" + +pass "ThinLTO helpers sharing a demangled name paired up correctly" -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 77+ messages in thread
* Re: [PATCH 58/58] objtool/klp: Add test for ThinLTO symbols sharing a demangled name 2026-09-11 18:50 ` [PATCH 58/58] objtool/klp: Add test for ThinLTO symbols sharing a demangled name Song Liu @ 2026-09-11 19:28 ` sashiko-bot 0 siblings, 0 replies; 77+ messages in thread From: sashiko-bot @ 2026-09-11 19:28 UTC (permalink / raw) To: Song Liu, live-patching Cc: jikos, pmladek, peterz, puranjay, jpoimboe, mbenes, kernel-team, joe.lawrence Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] The file header comment contradicts the test's implementation and the commit message regarding how many helper functions are changed. -- commit 7c4c07d226394615e86d71545cba54395d452f6f Author: Song Liu <song@kernel.org> objtool/klp: Add test for ThinLTO symbols sharing a demangled name This commit introduces a new objtool/klp test to verify that ThinLTO-promoted file-local symbols sharing the same demangled name are paired correctly when calculating klp diffs. > diff --git a/tools/objtool/tests/generic/fixtures/thinlto_ambiguity.c b/tools/objtool/tests/generic/fixtures/thinlto_ambiguity.c > new file mode 100644 > index 0000000000000..4a87bc92ea2ee > --- /dev/null > +++ b/tools/objtool/tests/generic/fixtures/thinlto_ambiguity.c > @@ -0,0 +1,55 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Three translation units linked with ThinLTO, two of which have a file-local > + * helper of the same name. > + * > + * TU_C calls into both of the others, so ThinLTO imports entry_a and entry_b > + * and with them the static helper each one calls. A file-local symbol which > + * has to become visible is renamed helper.llvm.<hash>, and the hash is content > + * derived -- so the two helpers get different hashes from each other, and > + * different ones again after the patch changes them both. [Severity: Low] Is this description accurate regarding how many helpers change? The comment says "after the patch changes them both", which appears to contradict both the commit message and the later comment in this same block that explicitly states "Only TU_A's helper changes." Could this be a stale comment from a previous version of the test? > + * > + * That leaves klp diff with two symbols in the original and two in the patched > + * object, all four named differently, which have to be paired up correctly. > + * Demangling alone gives "helper" for all of them; something else has to > + * decide which is which. > + * > + * Only TU_A's helper changes. That is what makes a wrong pairing observable: -- Sashiko AI review · https://sashiko.dev/#/patchset/20260911185031.1534046-1-song@kernel.org?part=58 ^ permalink raw reply [flat|nested] 77+ messages in thread
* Re: [PATCH 00/58] Unit test framework for klp-build toolchain 2026-09-11 18:42 [PATCH 00/58] Unit test framework for klp-build toolchain Song Liu ` (25 preceding siblings ...) 2026-09-11 18:49 ` [PATCH 26/58] objtool/klp: Add test for unchecksummed input Song Liu @ 2026-09-13 1:53 ` Josh Poimboeuf 26 siblings, 0 replies; 77+ messages in thread From: Josh Poimboeuf @ 2026-09-13 1:53 UTC (permalink / raw) To: Song Liu Cc: live-patching, peterz, jikos, mbenes, pmladek, joe.lawrence, puranjay, kernel-team On Fri, Sep 11, 2026 at 11:42:07AM -0700, Song Liu wrote: > This is the unit test part of a test framework for the klp-build toolchain. > The other part of the test framework will be some integration tests, > similar to an earlier propose [1]. > > This is joint work by Puranjay Mohan, Joe Lawrence, and myself (Song Liu). > Note that, while only two patches here are under Joe's authorship, his > contribution to this set is much bigger than the number represents. A lot > of these tests are based on Joe's earlier work in this area [2]. > > As the Assisted-by tags show, a lot of the work here is done with assists > of AI. We actually designed the framework to so that we expect AI to write > these tests in the future. AI also contributed in the design of the test > framework. However, we (human) carefully reviewed the test framework. Also, > this cover letter is 100% written by a human being (Song Liu). > > Here are some key design choices we made for this unit tests framework: > > 1. We do not need different unit test here for different kernel CONFIG_* > options. Instead, the tests cover different CONFIG_* options by passing > different arguments to the compiler and objtool. > > 2. The test is NOT designed to be backward compatible. Similar to objtool > itself, the unit tests are delivered with the kernel tree. The user of > these tests should test the objtool with the unit tests in the same > branch. Once this test framework lands, future changes and fixes to the > objtool klp command should land with unit tests that cover the changes. > > 3. It is possible to run these tests on a different architecture with > proper cross compilers. However, for automated CI, we are planning to > focus on native tests. Please refer to "Future work" section below for > more information about the CI. > > 4. AI will help write tests. We found AI is very cable to write good tests. > tools/objtool/Documentation/klp-write-tests.txt is added for AI to read, > so that AI can write tests for this test framework. > > Future work > 1. The integration test simiar to [1]. Unlike the unit tests, the > integration tests will need separate runs to cover different kernel > CONFIG_* combinations. > > 2. CI. The plan is to build a lore => patchwork => GitHub CI framework. > This is the model used by subsystems such as netdev, bpf, and risc-v. > We also have some ideas on "who will pay for the CI" issue, but I would > rather not promise anything yet. > > 3. More tests. > > Thanks, > Song > > > [1] https://lore.kernel.org/live-patching/20260226005436.379303-9-song@kernel.org/ > [2] https://github.com/joe-lawrence/klp-build-unit-tests Thank y'all for doing this, this is really nice, and very much needed! I would like to merge these soon, once the sashiko comments are addressed. -- Josh ^ permalink raw reply [flat|nested] 77+ messages in thread
end of thread, other threads:[~2026-09-13 1:53 UTC | newest] Thread overview: 77+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-09-11 18:42 [PATCH 00/58] Unit test framework for klp-build toolchain Song Liu 2026-09-11 18:42 ` [PATCH 01/58] objtool: Add test harness for the klp subcommands Song Liu 2026-09-11 18:42 ` [PATCH 02/58] objtool/klp: Check the klp test environment once, before any test Song Liu 2026-09-11 19:02 ` sashiko-bot 2026-09-11 18:42 ` [PATCH 03/58] objtool/klp: Group the klp tests by architecture Song Liu 2026-09-11 18:42 ` [PATCH 04/58] objtool/klp: Classify klp test outcomes Song Liu 2026-09-11 18:42 ` [PATCH 05/58] objtool/klp: Build klp test fixtures through the harness Song Liu 2026-09-11 18:42 ` [PATCH 06/58] objtool/klp: Grow the klp test harness vocabulary Song Liu 2026-09-11 19:03 ` sashiko-bot 2026-09-11 18:42 ` [PATCH 07/58] objtool/klp: Give each run one working directory, one per test inside it Song Liu 2026-09-11 18:42 ` [PATCH 08/58] objtool/klp: Run the klp tests under set -u Song Liu 2026-09-11 18:42 ` [PATCH 09/58] objtool/klp: Document the klp test harness Song Liu 2026-09-11 19:00 ` sashiko-bot 2026-09-11 18:42 ` [PATCH 10/58] objtool: Keep failing test workdirs by default Song Liu 2026-09-11 19:07 ` sashiko-bot 2026-09-11 18:42 ` [PATCH 11/58] objtool: Forward toolchain variables to the klp test runner Song Liu 2026-09-11 18:42 ` [PATCH 12/58] objtool/klp: Add test for rejecting changed data Song Liu 2026-09-11 18:42 ` [PATCH 13/58] objtool/klp: Add test for newly introduced data Song Liu 2026-09-11 19:07 ` sashiko-bot 2026-09-11 18:42 ` [PATCH 14/58] objtool/klp: Add test for newly introduced functions Song Liu 2026-09-11 18:42 ` [PATCH 15/58] objtool/klp: Add test for static local correlation Song Liu 2026-09-11 18:42 ` [PATCH 16/58] objtool/klp: Add test for cold function halves Song Liu 2026-09-11 19:07 ` sashiko-bot 2026-09-11 18:42 ` [PATCH 17/58] objtool/klp: Add test for special section extraction Song Liu 2026-09-11 18:42 ` [PATCH 18/58] objtool/klp: Add test for selective " Song Liu 2026-09-11 18:42 ` [PATCH 19/58] objtool/klp: Add test for jump table key relocations Song Liu 2026-09-11 18:42 ` [PATCH 20/58] objtool/klp: Add test for rejecting module-owned static branch keys Song Liu 2026-09-11 18:42 ` [PATCH 21/58] objtool/klp: Add test for rejecting module-owned static call keys Song Liu 2026-09-11 18:42 ` [PATCH 22/58] objtool/klp: Add test for symids in discarded sections Song Liu 2026-09-11 18:42 ` [PATCH 23/58] objtool/klp: Add test for rejecting references to init code/data Song Liu 2026-09-11 19:18 ` sashiko-bot 2026-09-11 18:42 ` [PATCH 24/58] objtool/klp: Add test for correlation across ThinLTO name mangling Song Liu 2026-09-11 18:42 ` [PATCH 25/58] objtool/klp: Add test for objects without .modinfo Song Liu 2026-09-11 18:49 ` [PATCH 26/58] objtool/klp: Add test for unchecksummed input Song Liu 2026-09-11 18:50 ` [PATCH 27/58] objtool/klp: Add klp diff and post-link regression tests Song Liu 2026-09-11 18:50 ` [PATCH 28/58] objtool/klp: Add test for klp reloc section naming in module objects Song Liu 2026-09-11 18:50 ` [PATCH 29/58] objtool/klp: Add test for vmlinux relocs in a patched module Song Liu 2026-09-11 18:50 ` [PATCH 30/58] objtool/klp: Add test for Module.symvers path normalization Song Liu 2026-09-11 18:50 ` [PATCH 31/58] objtool/klp: Add test for the contents of the klp_funcs list Song Liu 2026-09-11 18:50 ` [PATCH 32/58] objtool/klp: Add test for EXPORT_SYMBOL_FOR_MODULES references Song Liu 2026-09-11 18:50 ` [PATCH 33/58] objtool/klp: Add test for new references to exported symbols Song Liu 2026-09-11 18:50 ` [PATCH 34/58] objtool/klp: Add test for empty x86 alternative replacements Song Liu 2026-09-11 18:50 ` [PATCH 35/58] objtool/klp: Add test for recorded checksum values Song Liu 2026-09-11 18:50 ` [PATCH 36/58] objtool/klp: Add test for position-independent checksums Song Liu 2026-09-11 19:16 ` sashiko-bot 2026-09-11 18:50 ` [PATCH 37/58] objtool/klp: Add test for sympos in module objects Song Liu 2026-09-11 19:21 ` sashiko-bot 2026-09-11 18:50 ` [PATCH 38/58] objtool/klp: Add test for sympos resolved against a linked vmlinux Song Liu 2026-09-11 18:50 ` [PATCH 39/58] objtool/klp: Add test for static locals which must not be correlated Song Liu 2026-09-11 18:50 ` [PATCH 40/58] objtool/klp: Add test for __bug_table, __ex_table and __mcount_loc extraction Song Liu 2026-09-11 19:20 ` sashiko-bot 2026-09-11 18:50 ` [PATCH 41/58] objtool/klp: Add test for kCFI prefix symbols and traps Song Liu 2026-09-11 19:21 ` sashiko-bot 2026-09-11 18:50 ` [PATCH 42/58] objtool/klp: Add test for symbols whose linkage the patch changes Song Liu 2026-09-11 18:50 ` [PATCH 43/58] objtool/klp: Test rejection of a file-local static branch key Song Liu 2026-09-11 18:50 ` [PATCH 44/58] objtool/klp: Test a hand-built livepatch module's static call keys Song Liu 2026-09-11 18:50 ` [PATCH 45/58] objtool/klp: Test text annotations on alternative replacements Song Liu 2026-09-11 18:50 ` [PATCH 46/58] objtool/klp: Add test for data object checksums Song Liu 2026-09-11 18:50 ` [PATCH 47/58] objtool/klp: Add test for symbols with no checksum entry of their own Song Liu 2026-09-11 19:23 ` sashiko-bot 2026-09-11 18:50 ` [PATCH 48/58] objtool/klp: Add test for a static branch introduced by the patch Song Liu 2026-09-11 18:50 ` [PATCH 49/58] objtool/klp: Add test for tracepoint and pr_debug static branch keys Song Liu 2026-09-11 18:50 ` [PATCH 50/58] objtool/klp: Add test for a static call introduced by the patch Song Liu 2026-09-11 19:25 ` sashiko-bot 2026-09-11 18:50 ` [PATCH 51/58] objtool/klp: Add test for instruction operand checksums Song Liu 2026-09-11 18:50 ` [PATCH 52/58] objtool/klp: Add test for alternative replacement code in checksums Song Liu 2026-09-11 19:30 ` sashiko-bot 2026-09-11 18:50 ` [PATCH 53/58] objtool/klp: Add test for the alignment of cloned data sections Song Liu 2026-09-11 18:50 ` [PATCH 54/58] objtool/klp: Add test for a patch which strips a data annotation Song Liu 2026-09-11 19:24 ` sashiko-bot 2026-09-11 18:50 ` [PATCH 55/58] objtool/klp: Add test for absolute and __ADDRESSABLE symbols Song Liu 2026-09-11 18:50 ` [PATCH 56/58] objtool/klp: Add test for UBSAN metadata in an unchanged function Song Liu 2026-09-11 18:50 ` [PATCH 57/58] objtool/klp: Add test for Clang switch jump tables Song Liu 2026-09-11 19:27 ` sashiko-bot 2026-09-11 18:50 ` [PATCH 58/58] objtool/klp: Add test for ThinLTO symbols sharing a demangled name Song Liu 2026-09-11 19:28 ` sashiko-bot 2026-09-13 1:53 ` [PATCH 00/58] Unit test framework for klp-build toolchain Josh Poimboeuf
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox