[PATCH v10 03/13] kbuild: add tristate checker

From: Nick Alcock
Date: Mon Dec 05 2022 - 11:33:00 EST


This new check target uses the tristate.conf machinery just added, and
modules.builtin.objs, to identify source files that have MODULE_*
declarations despite not being modules according to Kconfig. After
commit 8b41fc4454e ("kbuild: create modules.builtin without
Makefile.modbuiltin or tristate.conf"), all such declarations will cause
modprobe to misidentify their containing object file as a module when it
is not, which might cause it to spuriously fail when trying to load
something that is built in to the kernel.

Only source files compiled by the current configuration are checked, so
a make allyesconfig is probably a good thing to do before running this:
output if you don't do that won't be wrong but will probably be
incomplete. Note that it is quite possible for things to be modular on
only some architectures, in which case a make allyesconfig build will
give false positive reports on other arches. (This is rare, but does
apply to e.g. KVM on aarch64.)

The checking is done by comparing modules.builtin.obj to a new
check-tristates.objs, which is built in a way more or less identical to
the way modules.builtin used to be built before commit 8b41fc4454e; the
principal difference is that it's only built when running the checker,
so doesn't slow down existing builds.

This file is similar in structure to modules.builtin.objs, and
constructed in a similar way to the way modules.builtin used to be built
before commit 8b41fc4454e, via tristate.conf inclusion and recursive
concatenation up the tree.

Signed-off-by: Nick Alcock <nick.alcock@xxxxxxxxxx>
---

Notes:
v10: adapted from modules_thick.builtin code: adjusted to use
modules.builtin.objs, turned into a tristate checker. Top-level
Kbuild changes no longer necessary.

.gitignore | 1 +
Documentation/dontdiff | 1 +
Makefile | 23 ++++++++++++++--
scripts/Kbuild.include | 6 ++++
scripts/Makefile.lib | 8 +++++-
scripts/check-tristates.mk | 56 ++++++++++++++++++++++++++++++++++++++
6 files changed, 92 insertions(+), 3 deletions(-)
create mode 100644 scripts/check-tristates.mk

diff --git a/.gitignore b/.gitignore
index ef8665c64f21..75f246e0565f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -52,6 +52,7 @@
*.zst
Module.symvers
modules.order
+check-tristates.objs

#
# Top-level generic files
diff --git a/Documentation/dontdiff b/Documentation/dontdiff
index ed1fbc711f33..17686f59039c 100644
--- a/Documentation/dontdiff
+++ b/Documentation/dontdiff
@@ -183,6 +183,7 @@ modules.builtin
modules.builtin.*
modules.nsdeps
modules.order
+check-tristates.objs
modversions.h*
nconf
nconf-cfg
diff --git a/Makefile b/Makefile
index 248f780cb75b..4d8a4c231cc1 100644
--- a/Makefile
+++ b/Makefile
@@ -1689,6 +1689,7 @@ help:
@echo ' coccicheck - Check with Coccinelle'
@echo ' clang-analyzer - Check with clang static analyzer'
@echo ' clang-tidy - Check with clang-tidy'
+ @echo ' tristatecheck - Check for non-tristates with MODULE_ declarations'
@echo ''
@echo 'Tools:'
@echo ' nsdeps - Generate missing symbol namespace dependencies'
@@ -2012,7 +2013,7 @@ clean: $(clean-dirs)
-o -name '*.lex.c' -o -name '*.tab.[ch]' \
-o -name '*.asn1.[ch]' \
-o -name '*.symtypes' -o -name 'modules.order' \
- -o -name '.tmp_*' \
+ -o -name 'check-tristates.objs' -o -name '.tmp_*' \
-o -name '*.c.[012]*.*' \
-o -name '*.ll' \
-o -name '*.gcno' \
@@ -2082,7 +2083,25 @@ coccicheck:
export_report:
$(PERL) $(srctree)/scripts/export_report.pl

-PHONY += checkstack kernelrelease kernelversion image_name
+tristatecheck: SHELL=/bin/bash
+tristatecheck: check-tristates.objs modules.builtin.objs
+ $(Q) for name in $$(comm -23 <(sed 's,:,,' modules.builtin.objs | tr ' ' '\n' | sort -u) \
+ <(sed 's,:,,' check-tristates.objs | tr ' ' '\n' | sort -u)); do \
+ grep -w $$name modules.builtin.objs | while read -r line; do \
+ case $$line in \
+ $$name:\ ) printf "%s " "$$line" | sed 's,: ,,';; \
+ $$name:\ *) printf "%s " "$$line" | sed 's,^.*: ,,';; \
+ *) echo $$name;; \
+ esac; \
+ done; \
+ done | tr ' ' '\n' | sort -u | while read -r name; do \
+ test -f $${name%.o}.c && echo $${name%.o}.c; \
+ done | xargs grep -l '^ *MODULE_' || true
+
+check-tristates.objs: $(build-dir)
+ $(Q)$(MAKE) $(tristatecheck)=$^ tristates-file=$@
+
+PHONY += checkstack kernelrelease kernelversion image_name tristatecheck

# UML needs a little special treatment here. It wants to use the host
# toolchain, so needs $(SUBARCH) passed to checkstack.pl. Everyone
diff --git a/scripts/Kbuild.include b/scripts/Kbuild.include
index 2bc08ace38a3..8042c067312e 100644
--- a/scripts/Kbuild.include
+++ b/scripts/Kbuild.include
@@ -78,6 +78,12 @@ endef
# $(Q)$(MAKE) $(build)=dir
build := -f $(srctree)/scripts/Makefile.build obj

+###
+# Shorthand for $(Q)$(MAKE) -f scripts/check-tristates.mk need-tristates=1 obj=
+# Usage:
+# $(Q)$(MAKE) $(tristatecheck)=dir
+tristatecheck := -f $(srctree)/scripts/check-tristates.mk need-tristates=1 obj
+
###
# Shorthand for $(Q)$(MAKE) -f scripts/Makefile.dtbinst obj=
# Usage:
diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib
index f7e5a83572fa..3bbfa99259cd 100644
--- a/scripts/Makefile.lib
+++ b/scripts/Makefile.lib
@@ -39,11 +39,17 @@ else
obj-m := $(filter-out %/, $(obj-m))
endif

-ifdef need-builtin
+ifdef need-tristates
+subdir-ym := $(sort $(subdir-y) $(subdir-m) \
+ $(patsubst %/,%, $(filter %/, $(obj-y) $(obj-m) $(obj-Y) $(obj-M))))
obj-y := $(patsubst %/, %/built-in.a, $(obj-y))
else
+ifdef need-builtin
+obj-y := $(patsubst %/, %/built-in.a, $(obj-y))
+else
obj-y := $(filter-out %/, $(obj-y))
endif
+endif

# Expand $(foo-objs) $(foo-y) etc. by replacing their individuals
suffix-search = $(strip $(foreach s, $3, $($(1:%$(strip $2)=%$s))))
diff --git a/scripts/check-tristates.mk b/scripts/check-tristates.mk
new file mode 100644
index 000000000000..fa9fadb79143
--- /dev/null
+++ b/scripts/check-tristates.mk
@@ -0,0 +1,56 @@
+# SPDX-License-Identifier: GPL-2.0
+# ==========================================================================
+# Generating check-tristates.objs
+# ==========================================================================
+
+src := $(obj)
+
+PHONY := __tristates
+__tristates:
+
+include include/config/auto.conf
+# tristate.conf sets tristate variables to uppercase 'Y' or 'M'
+# That way, we get the list of built-in modules in obj-Y
+include include/config/tristate.conf
+
+include scripts/Kbuild.include
+
+ifdef building_out_of_srctree
+# Create output directory if not already present
+_dummy := $(shell [ -d $(obj) ] || mkdir -p $(obj))
+endif
+
+# The filename Kbuild has precedence over Makefile
+kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
+kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
+include $(kbuild-file)
+
+include scripts/Makefile.lib
+
+check-tristates-subdirs := $(patsubst %,%/check-tristates.objs, $(subdir-ym))
+check-tristates-target := $(obj)/check-tristates.objs
+
+__tristates: $(obj)/$(tristates-file) $(subdir-ym)
+ @:
+
+$(check-tristates-target): $(subdir-ym) FORCE
+ $(Q) rm -f $@
+ $(Q) $(foreach mod-o, $(filter %.o,$(obj-Y)),\
+ printf "%s: " $(addprefix $(obj)/,$(mod-o)) >> $@; \
+ printf " %s" $(sort $(strip $(addprefix $(obj)/,$($(mod-o:.o=-objs)) \
+ $($(mod-o:.o=-y)) $($(mod-o:.o=-Y))))) >> $@; \
+ printf "\n" >> $@; ) \
+ cat /dev/null $(check-tristates-subdirs) >> $@;
+
+PHONY += FORCE
+
+FORCE:
+
+# Descending
+# ---------------------------------------------------------------------------
+
+PHONY += $(subdir-ym)
+$(subdir-ym):
+ $(Q)$(MAKE) $(tristatecheck)=$@ tristates-file=$(tristates-file)
+
+.PHONY: $(PHONY)
--
2.38.0.266.g481848f278