[PATCH v2] selftests: stop leaking OUTPUT into gen_mods_dir kernel builds

From: Jiangshan Yi

Date: Fri Jul 17 2026 - 11:02:33 EST


selftests/Makefile builds each test with OUTPUT on the make command line:

$(MAKE) OUTPUT=$$BUILD/$$TARGET -C $$TARGET

That OUTPUT is authoritative (command-line origin) and travels through
MAKEFLAGS into every sub-make. For tests that set TEST_GEN_MODS_DIR
(currently mm/page_frag and livepatch/test_modules) the gen_mods_dir rule
recurses into a kernel module build:

$(MAKE) -C $(TEST_GEN_MODS_DIR)
# -> make -C $(KDIR) M=... modules

which may in turn (re)build tools/bpf/resolve_btfids as a kernel "prepare"
dependency (CONFIG_DEBUG_INFO_BTF). resolve_btfids derives its OUTPUT from
O= via tools/scripts/Makefile.include:

OUTPUT := $(ABSOLUTE_O)/$(if $(subdir),$(subdir)/)

but a command-line OUTPUT inherited from kselftest overrides that
assignment, so resolve_btfids builds into the kselftest test directory.
Because that directory lacks a trailing '/', the tools/build pattern rules
$(OUTPUT)%.o are corrupted as well: ".../mmmain.o" instead of
".../mm/main.o", ending in:

ld: cannot find .../resolve_btfids-in.o: No such file or directory

Fix this at the source of the leak: strip OUTPUT from the command-line
overrides handed to the gen_mods_dir/clean_mods_dir sub-make only. OUTPUT,
if still present, then reaches the kernel build merely as an overridable
environment default, which the tools build overrides with its O=-derived
value (which always carries a trailing '/'). The assignment is
target-specific, so other selftest sub-makes -- and the test programs that
rely on OUTPUT -- are unaffected; selftests that build resolve_btfids
themselves with an explicit OUTPUT= (bpf, hid) do not use gen_mods_dir and
are untouched.

This supersedes the earlier attempt to normalise OUTPUT with an override in
tools/bpf/resolve_btfids/Makefile. That override lives in the outer
resolve_btfids make and is invisible to the nested
"$(MAKE) $(build)=resolve_btfids" sub-make in which the pattern rules
actually run, so it did not fix the corruption; it also risked the
explicit-OUTPUT builds used by bpf/hid.

Reproduce the leak (before this fix), e.g. with a kernel configured for
CONFIG_DEBUG_INFO_BTF and a TEST_GEN_MODS_DIR test such as mm/page_frag:

$ make kselftest-all
...
HOSTLD .../mmresolve_btfids-in.o
gcc: error: .../mm/resolve_btfids-in.o: No such file or directory

Tested:
* With a real lib.mk gen_mods_dir descent, OUTPUT no longer reaches a
resolve_btfids-style sub-make as command-line (origin becomes
environment); the O=-derived "OUTPUT := " assignment then wins.
* make -C tools/bpf/resolve_btfids
make -C tools/bpf/resolve_btfids OUTPUT=/tmp/o/resolve_btfids/
both still build.
* make OUTPUT=/tmp/build/kcmp -C tools/testing/selftests/kcmp still
honours OUTPUT for its own programs.

Signed-off-by: Jiangshan Yi <yijiangshan@xxxxxxxxxx>

---
v2:
- Reworked the fix: instead of overriding OUTPUT in
tools/bpf/resolve_btfids/Makefile (v1), which is invisible to the
nested "$(MAKE) $(build)=resolve_btfids" sub-make where the pattern
rules actually run and also clobbers the explicit OUTPUT= used by
bpf/hid, strip OUTPUT from the command-line overrides of the
gen_mods_dir/clean_mods_dir sub-make in tools/testing/selftests/lib.mk
so the leak is fixed at its source.
v1:
- initial submission

tools/testing/selftests/lib.mk | 17 +++++++++++++++++
1 file changed, 17 insertions(+)

diff --git a/tools/testing/selftests/lib.mk b/tools/testing/selftests/lib.mk
index f02cc8a2e4ae..5345be76223a 100644
--- a/tools/testing/selftests/lib.mk
+++ b/tools/testing/selftests/lib.mk
@@ -142,9 +142,26 @@ else
@$(call RUN_TESTS, $(TEST_GEN_PROGS) $(TEST_CUSTOM_PROGS) $(TEST_PROGS))
endif

+# The build of TEST_GEN_MODS_DIR recurses into the kernel build (it builds
+# out-of-tree modules via "make -C \$(KDIR) M=..."), which may in turn build
+# tools/bpf/resolve_btfids as a kernel "prepare" dependency. That tools build
+# derives its own OUTPUT from O=, but a command-line OUTPUT inherited from here
+# (e.g. the per-test output directory that selftests/Makefile passes on its
+# command line) is authoritative and overrides that derivation, corrupting the
+# paths produced by the tools/build pattern rules (e.g. ".../mmmain.o" instead
+# of ".../mm/main.o", and a missing resolve_btfids-in.o).
+#
+# Strip OUTPUT from the command-line overrides handed to this sub-make only, so
+# it can no longer override the O=-derived value downstream. The variable, if
+# still present, then reaches the kernel build merely as an overridable
+# environment default, which the tools build ignores in favour of its O=-derived
+# OUTPUT. This is target-specific so that other sub-makes, which may legitimately
+# rely on the inherited OUTPUT, are unaffected.
+gen_mods_dir: MAKEOVERRIDES := $(filter-out OUTPUT=%,$(MAKEOVERRIDES))
gen_mods_dir:
$(Q)$(MAKE) -C $(TEST_GEN_MODS_DIR)

+clean_mods_dir: MAKEOVERRIDES := $(filter-out OUTPUT=%,$(MAKEOVERRIDES))
clean_mods_dir:
$(Q)$(MAKE) -C $(TEST_GEN_MODS_DIR) clean

--
2.25.1