[PATCH 4/4] kbuild: re-implement CONFIG_TRIM_UNUSED_KSYMS to make it work in one-pass

From: Masahiro Yamada
Date: Thu Feb 25 2021 - 11:05:36 EST


Commit a555bdd0c58c ("Kbuild: enable TRIM_UNUSED_KSYMS again, with some
guarding") re-enabled this feature, but Linus is still unhappy about
the build time.

The reason of the slowness is the recursion - after updating
<generated/autoksyms.h> (, which contains all symbols needed by modules),
Kbuild begins the second traverse, rebuilding objects whose EXPORT_SYMBOL
needs flipping.

This commit re-implements CONFIG_TRIM_UNUSED_KSYMS to make it work
in one pass. After the tree traverse, a linker script snippet
<generated/keep-ksyms.h> is generated. It feeds the list of necessary
sections to vmlinus.lds.S and modules.lds.S. The other sections fall
into DISCARDS.

There is no more build issue, I believe. I dropped the 'if EXPORT' and
'depends on !COMPILE_TEST' guarding.

Signed-off-by: Masahiro Yamada <masahiroy@xxxxxxxxxx>
---

Makefile | 30 +++-----
include/asm-generic/export.h | 23 ------
include/asm-generic/vmlinux.lds.h | 29 +++++--
include/linux/export.h | 54 +++----------
init/Kconfig | 3 +-
scripts/Makefile.build | 5 --
scripts/adjust_autoksyms.sh | 76 -------------------
.../{gen_autoksyms.sh => gen-keep-ksyms.sh} | 34 ++++++---
scripts/gen_ksymdeps.sh | 25 ------
scripts/module.lds.S | 38 +++++++---
10 files changed, 103 insertions(+), 214 deletions(-)
delete mode 100755 scripts/adjust_autoksyms.sh
rename scripts/{gen_autoksyms.sh => gen-keep-ksyms.sh} (78%)
delete mode 100755 scripts/gen_ksymdeps.sh

diff --git a/Makefile b/Makefile
index 34393fd72fe1..cda800fa2f78 100644
--- a/Makefile
+++ b/Makefile
@@ -1160,29 +1160,23 @@ export KBUILD_LDS := arch/$(SRCARCH)/kernel/vmlinux.lds
# used by scripts/Makefile.package
export KBUILD_ALLDIRS := $(sort $(filter-out arch/%,$(vmlinux-alldirs)) LICENSES arch include scripts tools)

-vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_OBJS) $(KBUILD_VMLINUX_LIBS)
+targets :=

-# Recurse until adjust_autoksyms.sh is satisfied
-PHONY += autoksyms_recursive
ifdef CONFIG_TRIM_UNUSED_KSYMS
# For the kernel to actually contain only the needed exported symbols,
# we have to build modules as well to determine what those symbols are.
# (this can be evaluated only once include/config/auto.conf has been included)
KBUILD_MODULES := 1

-autoksyms_recursive: descend modules.order
- $(Q)$(CONFIG_SHELL) $(srctree)/scripts/adjust_autoksyms.sh \
- "$(MAKE) -f $(srctree)/Makefile vmlinux"
-endif
-
-autoksyms_h := $(if $(CONFIG_TRIM_UNUSED_KSYMS), include/generated/autoksyms.h)
+quiet_cmd_gen_used_ksyms = GEN $@
+ cmd_gen_used_ksyms = $(CONFIG_SHELL) $(srctree)/scripts/gen-keep-ksyms.sh $< > $@

-quiet_cmd_autoksyms_h = GEN $@
- cmd_autoksyms_h = mkdir -p $(dir $@); \
- $(CONFIG_SHELL) $(srctree)/scripts/gen_autoksyms.sh $@
+include/generated/keep-ksyms.h: modules.order FORCE
+ $(call if_changed,gen_used_ksyms)
+targets += include/generated/keep-ksyms.h

-$(autoksyms_h):
- $(call cmd,autoksyms_h)
+$(KBUILD_LDS) modules_prepare: include/generated/keep-ksyms.h
+endif

$(KBUILD_LDS): prepare FORCE
$(Q)$(MAKE) $(build)=$(patsubst %/,%,$(dir $@)) $@
@@ -1194,11 +1188,11 @@ cmd_link-vmlinux = \
$(CONFIG_SHELL) $< "$(LD)" "$(KBUILD_LDFLAGS)" "$(LDFLAGS_vmlinux)"; \
$(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) $@, true)

-vmlinux: scripts/link-vmlinux.sh autoksyms_recursive $(KBUILD_LDS) \
+vmlinux: scripts/link-vmlinux.sh $(KBUILD_LDS) \
$(KBUILD_VMLINUX_OBJS) $(KBUILD_VMLINUX_LIBS) FORCE
+$(call if_changed,link-vmlinux)

-targets := vmlinux
+targets += vmlinux

# The actual objects are generated when descending,
# make sure no implicit rule kicks in
@@ -1227,7 +1221,7 @@ scripts: scripts_basic scripts_dtc
PHONY += prepare archprepare

archprepare: outputmakefile archheaders archscripts scripts include/config/kernel.release \
- asm-generic $(version_h) $(autoksyms_h) include/generated/utsrelease.h \
+ asm-generic $(version_h) include/generated/utsrelease.h \
include/generated/autoconf.h

prepare0: archprepare
@@ -1503,7 +1497,7 @@ endif # CONFIG_MODULES
# make distclean Remove editor backup files, patch leftover files and the like

# Directories & files removed with 'make clean'
-CLEAN_FILES += include/ksym vmlinux.symvers \
+CLEAN_FILES += vmlinux.symvers \
modules.builtin modules.builtin.modinfo modules.nsdeps \
compile_commands.json

diff --git a/include/asm-generic/export.h b/include/asm-generic/export.h
index e847f1fde367..b9be5b1dd7e6 100644
--- a/include/asm-generic/export.h
+++ b/include/asm-generic/export.h
@@ -57,30 +57,7 @@ __kstrtab_\name:
#endif
.endm

-#if defined(CONFIG_TRIM_UNUSED_KSYMS)
-
-#include <linux/kconfig.h>
-#include <generated/autoksyms.h>
-
-.macro __ksym_marker sym
- .section ".discard.ksym","a"
-__ksym_marker_\sym:
- .previous
-.endm
-
-#define __EXPORT_SYMBOL(sym, val, sec) \
- __ksym_marker sym; \
- __cond_export_sym(sym, val, sec, __is_defined(__KSYM_##sym))
-#define __cond_export_sym(sym, val, sec, conf) \
- ___cond_export_sym(sym, val, sec, conf)
-#define ___cond_export_sym(sym, val, sec, enabled) \
- __cond_export_sym_##enabled(sym, val, sec)
-#define __cond_export_sym_1(sym, val, sec) ___EXPORT_SYMBOL sym, val, sec
-#define __cond_export_sym_0(sym, val, sec) /* nothing */
-
-#else
#define __EXPORT_SYMBOL(sym, val, sec) ___EXPORT_SYMBOL sym, val, sec
-#endif

#define EXPORT_SYMBOL(name) \
__EXPORT_SYMBOL(name, KSYM_FUNC(name),)
diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
index 5a2b31890bb8..f2b0990be159 100644
--- a/include/asm-generic/vmlinux.lds.h
+++ b/include/asm-generic/vmlinux.lds.h
@@ -50,6 +50,24 @@
* [__nosave_begin, __nosave_end] for the nosave data
*/

+#if CONFIG_TRIM_UNUSED_KSYMS
+#include <generated/keep-ksyms.h>
+
+#define KSYM_DISCARDS *(___ksymtab+*) \
+ *(___ksymtab_gpl+*) \
+ *(___kcrctab+*) \
+ *(___kcrctab_gpl+*) \
+ *(__ksymtab_strings+*)
+
+#else
+#define KSYMTAB KEEP(*(SORT(___ksymtab+*)))
+#define KSYMTAB_GPL KEEP(*(SORT(___ksymtab_gpl+*)))
+#define KCRCTAB KEEP(*(SORT(___kcrctab+*)))
+#define KCRCTAB_GPL KEEP(*(SORT(___kcrctab_gpl+*)))
+#define KSYMTAB_STRINGS *(__ksymtab_strings+*)
+#define KSYM_DISCARDS
+#endif
+
#ifndef LOAD_OFFSET
#define LOAD_OFFSET 0
#endif
@@ -486,34 +504,34 @@
/* Kernel symbol table: Normal symbols */ \
__ksymtab : AT(ADDR(__ksymtab) - LOAD_OFFSET) { \
__start___ksymtab = .; \
- KEEP(*(SORT(___ksymtab+*))) \
+ KSYMTAB \
__stop___ksymtab = .; \
} \
\
/* Kernel symbol table: GPL-only symbols */ \
__ksymtab_gpl : AT(ADDR(__ksymtab_gpl) - LOAD_OFFSET) { \
__start___ksymtab_gpl = .; \
- KEEP(*(SORT(___ksymtab_gpl+*))) \
+ KSYMTAB_GPL \
__stop___ksymtab_gpl = .; \
} \
\
/* Kernel symbol table: Normal symbols */ \
__kcrctab : AT(ADDR(__kcrctab) - LOAD_OFFSET) { \
__start___kcrctab = .; \
- KEEP(*(SORT(___kcrctab+*))) \
+ KCRCTAB \
__stop___kcrctab = .; \
} \
\
/* Kernel symbol table: GPL-only symbols */ \
__kcrctab_gpl : AT(ADDR(__kcrctab_gpl) - LOAD_OFFSET) { \
__start___kcrctab_gpl = .; \
- KEEP(*(SORT(___kcrctab_gpl+*))) \
+ KCRCTAB_GPL \
__stop___kcrctab_gpl = .; \
} \
\
/* Kernel symbol table: strings */ \
__ksymtab_strings : AT(ADDR(__ksymtab_strings) - LOAD_OFFSET) { \
- *(__ksymtab_strings+*) \
+ KSYMTAB_STRINGS \
} \
\
/* __*init sections */ \
@@ -993,6 +1011,7 @@
/DISCARD/ : { \
EXIT_DISCARDS \
EXIT_CALL \
+ KSYM_DISCARDS \
COMMON_DISCARDS \
}

diff --git a/include/linux/export.h b/include/linux/export.h
index 01e6ab19b226..f9cc13cd2c8c 100644
--- a/include/linux/export.h
+++ b/include/linux/export.h
@@ -76,9 +76,18 @@ struct kernel_symbol {
};
#endif

-#ifdef __GENKSYMS__
+#if !defined(CONFIG_MODULES) || defined(__DISABLE_EXPORTS)
+
+/*
+ * Allow symbol exports to be disabled completely so that C code may
+ * be reused in other execution contexts such as the UEFI stub or the
+ * decompressor.
+ */
+#define __EXPORT_SYMBOL(sym, sec, ns)
+
+#elif defined(__GENKSYMS__)

-#define ___EXPORT_SYMBOL(sym, sec, ns) __GENKSYMS_EXPORT_SYMBOL(sym)
+#define __EXPORT_SYMBOL(sym, sec, ns) __GENKSYMS_EXPORT_SYMBOL(sym)

#else

@@ -94,7 +103,7 @@ struct kernel_symbol {
* section flag requires it. Use '%progbits' instead of '@progbits' since the
* former apparently works on all arches according to the binutils source.
*/
-#define ___EXPORT_SYMBOL(sym, sec, ns) \
+#define __EXPORT_SYMBOL(sym, sec, ns) \
extern typeof(sym) sym; \
extern const char __kstrtab_##sym[]; \
extern const char __kstrtabns_##sym[]; \
@@ -107,45 +116,6 @@ struct kernel_symbol {
" .previous \n"); \
__KSYMTAB_ENTRY(sym, sec)

-#endif
-
-#if !defined(CONFIG_MODULES) || defined(__DISABLE_EXPORTS)
-
-/*
- * Allow symbol exports to be disabled completely so that C code may
- * be reused in other execution contexts such as the UEFI stub or the
- * decompressor.
- */
-#define __EXPORT_SYMBOL(sym, sec, ns)
-
-#elif defined(CONFIG_TRIM_UNUSED_KSYMS)
-
-#include <generated/autoksyms.h>
-
-/*
- * For fine grained build dependencies, we want to tell the build system
- * about each possible exported symbol even if they're not actually exported.
- * We use a symbol pattern __ksym_marker_<symbol> that the build system filters
- * from the $(NM) output (see scripts/gen_ksymdeps.sh). These symbols are
- * discarded in the final link stage.
- */
-#define __ksym_marker(sym) \
- static int __ksym_marker_##sym[0] __section(".discard.ksym") __used
-
-#define __EXPORT_SYMBOL(sym, sec, ns) \
- __ksym_marker(sym); \
- __cond_export_sym(sym, sec, ns, __is_defined(__KSYM_##sym))
-#define __cond_export_sym(sym, sec, ns, conf) \
- ___cond_export_sym(sym, sec, ns, conf)
-#define ___cond_export_sym(sym, sec, ns, enabled) \
- __cond_export_sym_##enabled(sym, sec, ns)
-#define __cond_export_sym_1(sym, sec, ns) ___EXPORT_SYMBOL(sym, sec, ns)
-#define __cond_export_sym_0(sym, sec, ns) /* nothing */
-
-#else
-
-#define __EXPORT_SYMBOL(sym, sec, ns) ___EXPORT_SYMBOL(sym, sec, ns)
-
#endif /* CONFIG_MODULES */

#ifdef DEFAULT_SYMBOL_NAMESPACE
diff --git a/init/Kconfig b/init/Kconfig
index 351161326e3c..e52034f66aeb 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -2259,8 +2259,7 @@ config MODULE_ALLOW_MISSING_NAMESPACE_IMPORTS
If unsure, say N.

config TRIM_UNUSED_KSYMS
- bool "Trim unused exported kernel symbols" if EXPERT
- depends on !COMPILE_TEST
+ bool "Trim unused exported kernel symbols"
help
The kernel and some modules make many symbols available for
other modules to use via EXPORT_SYMBOL() and variants. Depending
diff --git a/scripts/Makefile.build b/scripts/Makefile.build
index fd573e5ca0b9..fd2d7517a652 100644
--- a/scripts/Makefile.build
+++ b/scripts/Makefile.build
@@ -245,16 +245,12 @@ objtool_dep = $(objtool_obj) \
include/config/stack/validation.h)

ifdef CONFIG_TRIM_UNUSED_KSYMS
-cmd_gen_ksymdeps = \
- $(CONFIG_SHELL) $(srctree)/scripts/gen_ksymdeps.sh $@ >> $(dot-target).cmd
-
# List module undefined symbols
undefined_syms = $(NM) $< | $(AWK) '$$1 == "U" { printf("%s%s", x++ ? " " : "", $$2) }';
endif

define rule_cc_o_c
$(call cmd_and_fixdep,cc_o_c)
- $(call cmd,gen_ksymdeps)
$(call cmd,checksrc)
$(call cmd,checkdoc)
$(call cmd,objtool)
@@ -264,7 +260,6 @@ endef

define rule_as_o_S
$(call cmd_and_fixdep,as_o_S)
- $(call cmd,gen_ksymdeps)
$(call cmd,objtool)
$(call cmd,modversions_S)
endef
diff --git a/scripts/adjust_autoksyms.sh b/scripts/adjust_autoksyms.sh
deleted file mode 100755
index 2b366d945ccb..000000000000
--- a/scripts/adjust_autoksyms.sh
+++ /dev/null
@@ -1,76 +0,0 @@
-#!/bin/sh
-# SPDX-License-Identifier: GPL-2.0-only
-
-# Script to update include/generated/autoksyms.h and dependency files
-#
-# Copyright: (C) 2016 Linaro Limited
-# Created by: Nicolas Pitre, January 2016
-#
-
-# Update the include/generated/autoksyms.h file.
-#
-# For each symbol being added or removed, the corresponding dependency
-# file's timestamp is updated to force a rebuild of the affected source
-# file. All arguments passed to this script are assumed to be a command
-# to be exec'd to trigger a rebuild of those files.
-
-set -e
-
-cur_ksyms_file="include/generated/autoksyms.h"
-new_ksyms_file="include/generated/autoksyms.h.tmpnew"
-
-info() {
- if [ "$quiet" != "silent_" ]; then
- printf " %-7s %s\n" "$1" "$2"
- fi
-}
-
-info "CHK" "$cur_ksyms_file"
-
-# Use "make V=1" to debug this script.
-case "$KBUILD_VERBOSE" in
-*1*)
- set -x
- ;;
-esac
-
-# We need access to CONFIG_ symbols
-. include/config/auto.conf
-
-# Generate a new symbol list file
-$CONFIG_SHELL $srctree/scripts/gen_autoksyms.sh "$new_ksyms_file"
-
-# Extract changes between old and new list and touch corresponding
-# dependency files.
-changed=$(
-count=0
-sort "$cur_ksyms_file" "$new_ksyms_file" | uniq -u |
-sed -n 's/^#define __KSYM_\(.*\) 1/\1/p' | tr "A-Z_" "a-z/" |
-while read sympath; do
- if [ -z "$sympath" ]; then continue; fi
- depfile="include/ksym/${sympath}.h"
- mkdir -p "$(dirname "$depfile")"
- touch "$depfile"
- # Filesystems with coarse time precision may create timestamps
- # equal to the one from a file that was very recently built and that
- # needs to be rebuild. Let's guard against that by making sure our
- # dep files are always newer than the first file we created here.
- while [ ! "$depfile" -nt "$new_ksyms_file" ]; do
- touch "$depfile"
- done
- echo $((count += 1))
-done | tail -1 )
-changed=${changed:-0}
-
-if [ $changed -gt 0 ]; then
- # Replace the old list with tne new one
- old=$(grep -c "^#define __KSYM_" "$cur_ksyms_file" || true)
- new=$(grep -c "^#define __KSYM_" "$new_ksyms_file" || true)
- info "KSYMS" "symbols: before=$old, after=$new, changed=$changed"
- info "UPD" "$cur_ksyms_file"
- mv -f "$new_ksyms_file" "$cur_ksyms_file"
- # Then trigger a rebuild of affected source files
- exec $@
-else
- rm -f "$new_ksyms_file"
-fi
diff --git a/scripts/gen_autoksyms.sh b/scripts/gen-keep-ksyms.sh
similarity index 78%
rename from scripts/gen_autoksyms.sh
rename to scripts/gen-keep-ksyms.sh
index b74d5949fea6..cedb18fac46b 100755
--- a/scripts/gen_autoksyms.sh
+++ b/scripts/gen-keep-ksyms.sh
@@ -1,13 +1,23 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0-only

-# Create an autoksyms.h header file from the list of all module's needed symbols
-# as recorded on the second line of *.mod files and the user-provided symbol
-# whitelist.
-
set -e

-output_file="$1"
+modlist=$1
+
+emit ()
+{
+ local macro="$1"
+ local prefix="$2"
+ local syms="$3"
+
+ echo "#define $macro \\"
+ for s in $syms
+ do
+ echo " KEEP(*($prefix$s)) \\"
+ done
+ echo
+}

# Use "make V=1" to debug this script.
case "$KBUILD_VERBOSE" in
@@ -49,15 +59,14 @@ fi

# Generate a new ksym list file with symbols needed by the current
# set of modules.
-cat > "$output_file" << EOT
+cat << EOT
/*
* Automatically generated file; DO NOT EDIT.
*/

EOT

-[ -f modules.order ] && modlist=modules.order || modlist=/dev/null
-
+syms=$(
{
sed 's/ko$/mod/' $modlist | xargs -n1 sed -n -e '2p'
echo "$needed_symbols"
@@ -67,4 +76,11 @@ EOT
# point addresses.
sed -e 's/^\.//' |
sort -u |
-sed -e 's/\(.*\)/#define __KSYM_\1 1/' >> "$output_file"
+sed -e 's/\(.*\)/\1/'
+)
+
+emit "KSYMTAB" "___ksymtab+" "$syms"
+emit "KSYMTAB_GPL" "___ksymtab_gpl+" "$syms"
+emit "KCRCTAB" "___kcrctab_gpl+" "$syms"
+emit "KCRCTAB_GPL" "___kcrctab_gpl+" "$syms"
+emit "KSYMTAB_STRINGS" "__ksymtab_strings+" "$syms"
diff --git a/scripts/gen_ksymdeps.sh b/scripts/gen_ksymdeps.sh
deleted file mode 100755
index 1324986e1362..000000000000
--- a/scripts/gen_ksymdeps.sh
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/bin/sh
-# SPDX-License-Identifier: GPL-2.0
-
-set -e
-
-# List of exported symbols
-ksyms=$($NM $1 | sed -n 's/.*__ksym_marker_\(.*\)/\1/p' | tr A-Z a-z)
-
-if [ -z "$ksyms" ]; then
- exit 0
-fi
-
-echo
-echo "ksymdeps_$1 := \\"
-
-for s in $ksyms
-do
- echo $s | sed -e 's:^_*: $(wildcard include/ksym/:' \
- -e 's:__*:/:g' -e 's/$/.h) \\/'
-done
-
-echo
-echo "$1: \$(ksymdeps_$1)"
-echo
-echo "\$(ksymdeps_$1):"
diff --git a/scripts/module.lds.S b/scripts/module.lds.S
index 168cd27e6122..a6d2d96e29f0 100644
--- a/scripts/module.lds.S
+++ b/scripts/module.lds.S
@@ -3,16 +3,30 @@
* Archs are free to supply their own linker scripts. ld will
* combine them automatically.
*/
-SECTIONS {
- /DISCARD/ : {
- *(.discard)
- *(.discard.*)
- }

- __ksymtab 0 : { *(SORT(___ksymtab+*)) }
- __ksymtab_gpl 0 : { *(SORT(___ksymtab_gpl+*)) }
- __kcrctab 0 : { *(SORT(___kcrctab+*)) }
- __kcrctab_gpl 0 : { *(SORT(___kcrctab_gpl+*)) }
+#if CONFIG_TRIM_UNUSED_KSYMS
+#include <generated/keep-ksyms.h>
+
+#define KSYM_DISCARDS *(___ksymtab+*) \
+ *(___ksymtab_gpl+*) \
+ *(___kcrctab+*) \
+ *(___kcrctab_gpl+*) \
+ *(__ksymtab_strings+*)
+#else
+#define KSYMTAB KEEP(*(SORT(___ksymtab+*)))
+#define KSYMTAB_GPL KEEP(*(SORT(___ksymtab_gpl+*)))
+#define KCRCTAB KEEP(*(SORT(___kcrctab+*)))
+#define KCRCTAB_GPL KEEP(*(SORT(___kcrctab_gpl+*)))
+#define KSYMTAB_STRINGS *(__ksymtab_strings+*)
+#define KSYM_DISCARDS
+#endif
+
+SECTIONS {
+ __ksymtab 0 : { KSYMTAB }
+ __ksymtab_gpl 0 : { KSYMTAB_GPL }
+ __kcrctab 0 : { KCRCTAB }
+ __kcrctab_gpl 0 : { KCRCTAB_GPL }
+ __ksymtab_strings 0 : { KSYMTAB_STRINGS }

.init_array 0 : ALIGN(8) { *(SORT(.init_array.*)) *(.init_array) }

@@ -41,6 +55,12 @@ SECTIONS {
}

.text : { *(.text .text.[0-9a-zA-Z_]*) }
+
+ /DISCARD/ : {
+ *(.discard)
+ *(.discard.*)
+ KSYM_DISCARDS
+ }
}

/* bring in arch-specific sections */
--
2.27.0