From f9f31f5f13b5e5459e2d3322fe6b5e19d511919a Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Sun, 16 Aug 2026 22:53:54 +0100 Subject: [PATCH 01/59] kconfig: error out for recursive range Currently, it is possible to create recursive ranges with Kconfig, such as: config C int "Enable C" default 1 range A B config A int "Enable A" default 2 range B C config B int "Enable B" default 3 range A C The current outcome of this example above, is that all 3 options are set to 2. There is currently no warning or error if a user attempts this. Several attributes are already checked for recursion, including 'depends on', 'select', and 'imply', and we can extend this to 'range' for numeric options (int and hex types). Recursive ranges are currently not used anywhere in the tree, so it is already safe to make this a hard error. Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Julian Braha Link: https://patch.msgid.link/20260816215354.1499059-1-julianbraha@gmail.com Signed-off-by: Nathan Chancellor --- scripts/kconfig/symbol.c | 5 +++- .../kconfig/tests/err_recursive_dep/Kconfig | 24 +++++++++++++++++++ .../tests/err_recursive_dep/expected_stderr | 13 ++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/scripts/kconfig/symbol.c b/scripts/kconfig/symbol.c index 7e81b3676ee9..e82f330fb8ee 100644 --- a/scripts/kconfig/symbol.c +++ b/scripts/kconfig/symbol.c @@ -1196,6 +1196,7 @@ static struct symbol *sym_check_expr_deps(const struct expr *e) case E_GTH: case E_LEQ: case E_LTH: + case E_RANGE: case E_UNEQUAL: sym = sym_check_deps(e->left.sym); if (sym) @@ -1243,7 +1244,9 @@ static struct symbol *sym_check_sym_deps(struct symbol *sym) sym2 = sym_check_expr_deps(prop->visible.expr); if (sym2) break; - if (prop->type != P_DEFAULT || sym_is_choice(sym)) + if (sym_is_choice(sym)) + continue; + if (prop->type != P_DEFAULT && prop->type != P_RANGE) continue; stack.expr = &prop->expr; sym2 = sym_check_expr_deps(prop->expr); diff --git a/scripts/kconfig/tests/err_recursive_dep/Kconfig b/scripts/kconfig/tests/err_recursive_dep/Kconfig index ebdb3ffd8717..bd1ff92bcab2 100644 --- a/scripts/kconfig/tests/err_recursive_dep/Kconfig +++ b/scripts/kconfig/tests/err_recursive_dep/Kconfig @@ -61,3 +61,27 @@ config G bool "G" endmenu + +# depends on and range + +config H1 + int + depends on H2 < H3 + +config H2 + int + range H3 H1 + +config H3 + int + range H1 H2 + +# range + +config I1 + int + range I2 I2 + +config I2 + int + range I1 I1 diff --git a/scripts/kconfig/tests/err_recursive_dep/expected_stderr b/scripts/kconfig/tests/err_recursive_dep/expected_stderr index fc2e860af082..08f843087678 100644 --- a/scripts/kconfig/tests/err_recursive_dep/expected_stderr +++ b/scripts/kconfig/tests/err_recursive_dep/expected_stderr @@ -36,3 +36,16 @@ error: recursive dependency detected! symbol G depends on G For a resolution refer to Documentation/kbuild/kconfig-language.rst subsection "Kconfig recursive dependency limitations" + +error: recursive dependency detected! + symbol H1 depends on H2 + symbol H2 range value contains H3 + symbol H3 range value contains H1 +For a resolution refer to Documentation/kbuild/kconfig-language.rst +subsection "Kconfig recursive dependency limitations" + +error: recursive dependency detected! + symbol I1 range value contains I2 + symbol I2 range value contains I1 +For a resolution refer to Documentation/kbuild/kconfig-language.rst +subsection "Kconfig recursive dependency limitations" From 72c442e5bad35552d902312402121682f05220ac Mon Sep 17 00:00:00 2001 From: Bhaskar Chowdhury Date: Wed, 19 Aug 2026 02:13:51 +0530 Subject: [PATCH 02/59] scripts/mkcompile_h: Update LINUX_COMPILE_HOST command substitution syntax The assignment of LINUX_COMPILE_HOST uses the classic backtick style for its command substitution but the rest of the script uses the more common '$()'. Update LINUX_COMPILE_HOST to match for consistency. Signed-off-by: Bhaskar Chowdhury Link: https://patch.msgid.link/20260818204804.2985008-1-unixbhaskar@gmail.com Signed-off-by: Nathan Chancellor --- scripts/mkcompile_h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/mkcompile_h b/scripts/mkcompile_h index 2596f78e52ef..bf0d5669d9bc 100755 --- a/scripts/mkcompile_h +++ b/scripts/mkcompile_h @@ -11,7 +11,7 @@ else LINUX_COMPILE_BY=$KBUILD_BUILD_USER fi if test -z "$KBUILD_BUILD_HOST"; then - LINUX_COMPILE_HOST=`uname -n` + LINUX_COMPILE_HOST=$(uname -n) else LINUX_COMPILE_HOST=$KBUILD_BUILD_HOST fi From ba0e6a6f0fe7a8ea2a35603cdf85e39d9239d492 Mon Sep 17 00:00:00 2001 From: Yuntao Wang Date: Sun, 23 Aug 2026 17:49:13 +0800 Subject: [PATCH 03/59] setlocalversion: make tag local to try_tag() The tag variable is only used by try_tag(), but is currently declared local to scm_version() and modified by try_tag(). Make tag local to try_tag() instead and remove the unused declaration from scm_version(). No functional changes. Signed-off-by: Yuntao Wang Link: https://patch.msgid.link/20260823094913.421144-1-yuntao.wang@linux.dev Signed-off-by: Nathan Chancellor --- scripts/setlocalversion | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/setlocalversion b/scripts/setlocalversion index 28169d7e143b..f264c2460ad7 100755 --- a/scripts/setlocalversion +++ b/scripts/setlocalversion @@ -33,7 +33,7 @@ if test $# -gt 0 -o ! -d "$srctree"; then fi try_tag() { - tag="$1" + local tag="$1" # Is $tag an annotated tag? if [ "$(git cat-file -t "$tag" 2> /dev/null)" != tag ]; then @@ -59,7 +59,6 @@ scm_version() { local short=false local no_dirty=false - local tag while [ $# -gt 0 ]; do From 7c1bcd3f127f3ba1a35faa15374222b6062c9712 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 19 Aug 2026 09:53:38 +0200 Subject: [PATCH 04/59] kbuild: use prefix-map for host and user progs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Host and user progs may also use __FILE__. Make sure it shows up canonicalized, as in regular kernel code. Signed-off-by: Thomas Weißschuh Reviewed-by: Nicolas Schier Tested-by: Nicolas Schier Link: https://patch.msgid.link/20260819-kbuild-prefix-map-v1-1-f5168b96fac0@linutronix.de Signed-off-by: Nathan Chancellor --- Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 4ad67b737af7..570f0c68aadd 100644 --- a/Makefile +++ b/Makefile @@ -1199,7 +1199,10 @@ export CFLAGS_GCOV # change __FILE__ to the relative path to the source directory ifdef building_out_of_srctree -KBUILD_CPPFLAGS += -fmacro-prefix-map=$(srcroot)/= +CFLAGS_PREFIX_MAP := -fmacro-prefix-map=$(srcroot)/= +KBUILD_CPPFLAGS += $(CFLAGS_PREFIX_MAP) +KBUILD_USERCFLAGS += $(CFLAGS_PREFIX_MAP) +KBUILD_HOSTCFLAGS += $(CFLAGS_PREFIX_MAP) ifeq ($(call rustc-option-yn, --remap-path-scope=macro),y) KBUILD_RUSTFLAGS += --remap-path-prefix=$(srcroot)/= --remap-path-scope=macro endif From 51794b107d54b3c9a8ebbe12a3b81d8495bd482c Mon Sep 17 00:00:00 2001 From: Jaihind Yadav Date: Thu, 20 Aug 2026 11:31:16 +0530 Subject: [PATCH 05/59] scripts/gcc-plugins: suppress recorded GCC switches for extmod builds With GCC plugins enabled, external module builds can record -fplugin=... paths in DWARF producer metadata via -grecord-gcc-switches, leaking host/build specific paths and triggering QA checks. Handle this in scripts/Makefile.gcc-plugins for KBUILD_EXTMOD builds by adding -gno-record-gcc-switches to plugin cflags. This keeps plugin behavior unchanged while avoiding absolute plugin path leakage in external module debug metadata. Suggested-by: Nathan Chancellor Link: https://lore.kernel.org/all/20260817185146.GC1249844@ax162/ Signed-off-by: Jaihind Yadav Link: https://patch.msgid.link/20260820060116.786173-1-jaihindy@qti.qualcomm.com Signed-off-by: Nathan Chancellor --- scripts/Makefile.gcc-plugins | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/Makefile.gcc-plugins b/scripts/Makefile.gcc-plugins index b0e1423b09c2..9b9899580571 100644 --- a/scripts/Makefile.gcc-plugins +++ b/scripts/Makefile.gcc-plugins @@ -8,6 +8,10 @@ ifdef CONFIG_GCC_PLUGIN_LATENT_ENTROPY endif export DISABLE_LATENT_ENTROPY_PLUGIN +# Disable recording GCC options in debug info when building external modules to +# avoid leaking absolute host build paths +gcc-plugin-cflags-$(if $(KBUILD_EXTMOD),y) += -gno-record-gcc-switches + # All the plugin CFLAGS are collected here in case a build target needs to # filter them out of the KBUILD_CFLAGS. GCC_PLUGINS_CFLAGS := $(strip $(addprefix -fplugin=$(objtree)/scripts/gcc-plugins/, $(gcc-plugin-y)) $(gcc-plugin-cflags-y)) -DGCC_PLUGINS From 68b2e64129185d6de3495ec8c73b219938115045 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 17:50:01 +0530 Subject: [PATCH 06/59] kbuild: fix typo "funtions" in comment Correct "funtions" to "functions", reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Fixes: 5270316c9fec ("kbuild: Use -fmin-function-alignment when available") Reviewed-by: Nicolas Schier Link: https://patch.msgid.link/20260904122002.27960-1-hemanth.selam@gmail.com Signed-off-by: Nathan Chancellor --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 570f0c68aadd..9bd226726358 100644 --- a/Makefile +++ b/Makefile @@ -1144,7 +1144,7 @@ export CC_FLAGS_NO_FPU ifneq ($(CONFIG_FUNCTION_ALIGNMENT),0) # Set the minimal function alignment. Use the newer GCC option -# -fmin-function-alignment if it is available, or fall back to -falign-funtions. +# -fmin-function-alignment if it is available, or fall back to -falign-functions. # See also CONFIG_CC_HAS_SANE_FUNCTION_ALIGNMENT. ifdef CONFIG_CC_HAS_MIN_FUNCTION_ALIGNMENT KBUILD_CFLAGS += -fmin-function-alignment=$(CONFIG_FUNCTION_ALIGNMENT) From 322b0463f30b0a02180c9528e38c2afb73d20fa0 Mon Sep 17 00:00:00 2001 From: Aayansh Ranjan Date: Tue, 15 Sep 2026 11:56:13 +0530 Subject: [PATCH 07/59] kbuild: fix grammar in output Makefile .gitignore generation comment Fix the grammar in the comment describing the generation of the output Makefile .gitignore file. Signed-off-by: Aayansh Ranjan Link: https://patch.msgid.link/20260915062613.13761-1-computergod47@gmail.com Signed-off-by: Nathan Chancellor --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 9bd226726358..2c6b04954bd8 100644 --- a/Makefile +++ b/Makefile @@ -681,8 +681,8 @@ ifdef building_out_of_srctree # outputmakefile generates a Makefile in the output directory, if using a # separate output directory. This allows convenient use of make in the # output directory. -# At the same time when output Makefile generated, generate .gitignore to -# ignore whole output directory +# At the same time as the output Makefile is generated, generate .gitignore to +# ignore the whole output directory ifdef KBUILD_EXTMOD print_env_for_makefile = \ From ff87968e4e564acb2fadce363b1b41e7fa3aad2f Mon Sep 17 00:00:00 2001 From: Erkan Erdem Date: Mon, 7 Sep 2026 13:37:37 +0300 Subject: [PATCH 08/59] docs: kconfig: fix shell function syntax in caveats Kconfig separates a function name from its arguments with a comma, but the caveats section uses Make-style whitespace in its shell calls. These expressions expand as undefined variables rather than invoking the shell function, so the supposedly working CC_HAS_ENDIAN_FLAG example fails to parse. Add the missing commas to the shell calls in this section. Keep the Make examples unchanged. Fixes: 316d55d55f49 ("Documentation: kconfig: document a new Kconfig macro language") Assisted-by: LLM Signed-off-by: Erkan Erdem Reviewed-by: Julian Braha Reviewed-by: Nicolas Schier Link: https://patch.msgid.link/20260907103739.23212-1-hexvalid@gmail.com Signed-off-by: Nathan Chancellor --- Documentation/kbuild/kconfig-macro-language.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Documentation/kbuild/kconfig-macro-language.rst b/Documentation/kbuild/kconfig-macro-language.rst index 6163467f6ae4..c427bece3148 100644 --- a/Documentation/kbuild/kconfig-macro-language.rst +++ b/Documentation/kbuild/kconfig-macro-language.rst @@ -225,7 +225,7 @@ not work:: $(MY_TYPE) "foo" default y -Obviously from the design, $(shell command) is expanded in the textual +Obviously from the design, $(shell,command) is expanded in the textual substitution phase. You cannot pass symbols to the 'shell' function. The following does not work as expected:: @@ -236,12 +236,12 @@ The following does not work as expected:: default "-mlittle-endian" if CPU_LITTLE_ENDIAN config CC_HAS_ENDIAN_FLAG - def_bool $(shell $(srctree)/scripts/gcc-check-flag ENDIAN_FLAG) + def_bool $(shell,$(srctree)/scripts/gcc-check-flag ENDIAN_FLAG) Instead, you can do like follows so that any function call is statically expanded:: config CC_HAS_ENDIAN_FLAG bool - default $(shell $(srctree)/scripts/gcc-check-flag -mbig-endian) if CPU_BIG_ENDIAN - default $(shell $(srctree)/scripts/gcc-check-flag -mlittle-endian) if CPU_LITTLE_ENDIAN + default $(shell,$(srctree)/scripts/gcc-check-flag -mbig-endian) if CPU_BIG_ENDIAN + default $(shell,$(srctree)/scripts/gcc-check-flag -mlittle-endian) if CPU_LITTLE_ENDIAN From dbe1ae2304a3b02c3bf6ae82895ff232a29f3fee Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Sun, 30 Aug 2026 23:11:29 +0100 Subject: [PATCH 09/59] kconfig: fix extra output from savedefconfig on out-of-range defaults The Kconfig interpreter currently allows defaults that are outside of the range bounds. In these cases, the 'sym_validate_range' function will adjust the default value to the nearest range bound. For example, see this example: config A int range 1 2 default 16 Here, since the default value of 16 is greater than the bounds, the effective default value gets adjusted down to the upper bound, 2. However, 'savedefconfig' writes non-default values, and without being aware of the automatic adjustment to the range bound, it would write: A=2 This limitation is also documented in a comment: "The following fails to handle the situation where a default value is further limited by the valid range." To resolve this, let's factor out the default-range adjustment logic from the existing 'sym_validate_range' function into its own 'sym_get_near_range_bound' function for 'savedefconfig' to use too, so that it compares against the effective value. Adds tests, accordingly. Fixes: 7cf3d73b4360 ("kconfig: add savedefconfig") Assisted-by: Codex:gpt-5.6-sol Reported-by: Geert Uytterhoeven Closes: https://lore.kernel.org/lkml/CAMuHMdVyUAA3L4mUkSjmnuE3cvj-+N8z-Bhxsh1wa-FQWc=fjw@mail.gmail.com/ Signed-off-by: Julian Braha Tested-by: Geert Uytterhoeven Reviewed-by: Nathan Chancellor Link: https://patch.msgid.link/20260830221129.2668354-1-julianbraha@gmail.com Signed-off-by: Nathan Chancellor --- scripts/kconfig/symbol.c | 39 ++++++++---- .../kconfig/tests/savedefconfig_range/Kconfig | 60 +++++++++++++++++++ .../tests/savedefconfig_range/__init__.py | 8 +++ .../kconfig/tests/savedefconfig_range/config | 7 +++ .../savedefconfig_range/expected_defconfig | 0 5 files changed, 102 insertions(+), 12 deletions(-) create mode 100644 scripts/kconfig/tests/savedefconfig_range/Kconfig create mode 100644 scripts/kconfig/tests/savedefconfig_range/__init__.py create mode 100644 scripts/kconfig/tests/savedefconfig_range/config create mode 100644 scripts/kconfig/tests/savedefconfig_range/expected_defconfig diff --git a/scripts/kconfig/symbol.c b/scripts/kconfig/symbol.c index e82f330fb8ee..01aa58d92c3b 100644 --- a/scripts/kconfig/symbol.c +++ b/scripts/kconfig/symbol.c @@ -157,7 +157,12 @@ static long long sym_get_range_val(struct symbol *sym, int base) return strtoll(sym->curr.val, NULL, base); } -static void sym_validate_range(struct symbol *sym) +/* + * Return the nearest range bound for an out-of-range default value. + * Return NULL if the value is valid or the symbol has no active range. + */ +static struct symbol *sym_get_near_range_bound(struct symbol *sym, + const char *value) { struct property *prop; struct symbol *range_sym; @@ -172,21 +177,31 @@ static void sym_validate_range(struct symbol *sym) base = 16; break; default: - return; + return NULL; } prop = sym_get_range_prop(sym); if (!prop) - return; - val = strtoll(sym->curr.val, NULL, base); + return NULL; + val = strtoll(value, NULL, base); range_sym = prop->expr->left.sym; val2 = sym_get_range_val(range_sym, base); if (val >= val2) { range_sym = prop->expr->right.sym; val2 = sym_get_range_val(range_sym, base); if (val <= val2) - return; + return NULL; } - sym->curr.val = range_sym->curr.val; + + return range_sym; +} + +static void sym_validate_range(struct symbol *sym) +{ + struct symbol *range_sym; + + range_sym = sym_get_near_range_bound(sym, sym->curr.val); + if (range_sym) + sym->curr.val = range_sym->curr.val; } static void sym_set_changed(struct symbol *sym) @@ -812,7 +827,7 @@ bool sym_set_string_value(struct symbol *sym, const char *newval) const char *sym_get_string_default(struct symbol *sym) { struct property *prop; - struct symbol *ds; + struct symbol *ds, *range_sym; const char *str = ""; tristate val; @@ -830,11 +845,6 @@ const char *sym_get_string_default(struct symbol *sym) val = EXPR_AND(expr_calc_value(prop->expr), prop->visible.tri); break; default: - /* - * The following fails to handle the situation - * where a default value is further limited by - * the valid range. - */ ds = prop_get_symbol(prop); if (ds != NULL) { sym_calc_value(ds); @@ -878,6 +888,11 @@ const char *sym_get_string_default(struct symbol *sym) default: break; } + + range_sym = sym_get_near_range_bound(sym, str); + if (range_sym) + str = range_sym->curr.val; + return str; } diff --git a/scripts/kconfig/tests/savedefconfig_range/Kconfig b/scripts/kconfig/tests/savedefconfig_range/Kconfig new file mode 100644 index 000000000000..fd59d9082ed5 --- /dev/null +++ b/scripts/kconfig/tests/savedefconfig_range/Kconfig @@ -0,0 +1,60 @@ +# SPDX-License-Identifier: GPL-2.0 + +# Static default and range values + +config INT_DEFAULT_ABOVE_RANGE + int + range 1 1 + default 16 + +config INT_DEFAULT_BELOW_RANGE + int + range 4 8 + default 2 + +# Default and range values determined by other options + +config RANGE_UPPER_BOUND + int + default 3 + +config INT_DYNAMIC_RANGE + int + range 0 RANGE_UPPER_BOUND + default 4 + +# Hex + +config HEX_DEFAULT_ABOVE_RANGE + hex + range 0x10 0x20 + default 0x40 + +# Implicit default value of 0 + +config INT_IMPLICIT_DEFAULT_ZERO + int + range 1 4 + +# Conditional range + +config USE_FIRST_RANGE + bool + default y + +config INT_CONDITIONAL_RANGE + int + range 1 2 if USE_FIRST_RANGE + range 3 4 if !USE_FIRST_RANGE + default 3 + +# Conditional default + +config USE_DEFAULT + bool + default y + +config INT_CONDITIONAL_DEFAULT + int + range 1 2 + default 3 if USE_DEFAULT diff --git a/scripts/kconfig/tests/savedefconfig_range/__init__.py b/scripts/kconfig/tests/savedefconfig_range/__init__.py new file mode 100644 index 000000000000..961454be732c --- /dev/null +++ b/scripts/kconfig/tests/savedefconfig_range/__init__.py @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +"""Test savedefconfig with numerical defaults outside of ranges.""" + + +def test(conf): + assert conf._run_conf('--savedefconfig=defconfig', dot_config='config', + out_file='defconfig') == 0 + assert conf.config_matches('expected_defconfig') diff --git a/scripts/kconfig/tests/savedefconfig_range/config b/scripts/kconfig/tests/savedefconfig_range/config new file mode 100644 index 000000000000..d939cfe5fd34 --- /dev/null +++ b/scripts/kconfig/tests/savedefconfig_range/config @@ -0,0 +1,7 @@ +CONFIG_INT_DEFAULT_ABOVE_RANGE=1 +CONFIG_INT_DEFAULT_BELOW_RANGE=4 +CONFIG_INT_DYNAMIC_RANGE=3 +CONFIG_HEX_DEFAULT_ABOVE_RANGE=0x20 +CONFIG_INT_IMPLICIT_DEFAULT_ZERO=1 +CONFIG_INT_CONDITIONAL_RANGE=2 +CONFIG_INT_CONDITIONAL_DEFAULT=2 diff --git a/scripts/kconfig/tests/savedefconfig_range/expected_defconfig b/scripts/kconfig/tests/savedefconfig_range/expected_defconfig new file mode 100644 index 000000000000..e69de29bb2d1 From a2a402bd5e5a4fa466d6034e70b18b6ac22f873d Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Thu, 17 Sep 2026 16:23:32 -0700 Subject: [PATCH 10/59] erofs: Revert "Fix EROFS_FS_ZIP_LZMA_DEFAULT_MAX_STREAMS default logic" With commit dbe1ae2304a3 ("kconfig: fix extra output from savedefconfig on out-of-range defaults") applied, commit ab74edaeb1ae ("erofs: Fix EROFS_FS_ZIP_LZMA_DEFAULT_MAX_STREAMS default logic") is no longer necessary, so revert it. Signed-off-by: Nathan Chancellor --- fs/erofs/Kconfig | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/fs/erofs/Kconfig b/fs/erofs/Kconfig index 37495e95a78d..2dfc313588d2 100644 --- a/fs/erofs/Kconfig +++ b/fs/erofs/Kconfig @@ -132,13 +132,11 @@ config EROFS_FS_ZIP_LZMA Say N if you want to disable LZMA compression support. config EROFS_FS_ZIP_LZMA_DEFAULT_MAX_STREAMS - int "EROFS LZMA default maximum decompression streams" if SMP + int "EROFS LZMA default maximum decompression streams" depends on EROFS_FS_ZIP_LZMA range 1 NR_CPUS if SMP range 1 1 if !SMP - default 16 if SMP && NR_CPUS >= 16 - default NR_CPUS if SMP - default 1 + default 16 help By default EROFS allocates one LZMA decompression stream per CPU. Each stream can hold a dictionary of up to 8 MiB taken from the @@ -146,7 +144,7 @@ config EROFS_FS_ZIP_LZMA_DEFAULT_MAX_STREAMS of memory. This caps the default; the lzma_streams module parameter still overrides it. - If unsure, keep the suggested default (16 or lower). + If unsure, keep the default of 16. config EROFS_FS_ZIP_DEFLATE bool "EROFS DEFLATE compressed data support" From 95fde4bcc0b7c78b2db6e89a43d47074b79f2afa Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Tue, 15 Sep 2026 22:15:04 +0100 Subject: [PATCH 11/59] kconfig: promote invalid numeric reference from warning to error The Kconfig interpreter already warns if a numeric option attempts to use a non-numeric option (bool, tristate, or string) to set its value (for example, with a 'default' or 'range'). Since there is nowhere in the tree that attempts this, we can safely promote this check from warning to error. Assisted-by: LLM Signed-off-by: Julian Braha Reviewed-by: Nicolas Schier Link: https://patch.msgid.link/20260915211508.291790-2-julianbraha@gmail.com Signed-off-by: Nathan Chancellor --- scripts/kconfig/lkc.h | 2 +- scripts/kconfig/menu.c | 39 ++++++---- scripts/kconfig/parser.y | 2 +- .../tests/err_num_non_numeric_ref/Kconfig | 75 +++++++++++++++++++ .../tests/err_num_non_numeric_ref/__init__.py | 9 +++ .../err_num_non_numeric_ref/expected_stderr | 14 ++++ 6 files changed, 126 insertions(+), 15 deletions(-) create mode 100644 scripts/kconfig/tests/err_num_non_numeric_ref/Kconfig create mode 100644 scripts/kconfig/tests/err_num_non_numeric_ref/__init__.py create mode 100644 scripts/kconfig/tests/err_num_non_numeric_ref/expected_stderr diff --git a/scripts/kconfig/lkc.h b/scripts/kconfig/lkc.h index 7e6f6ca299cf..bbc99f75b416 100644 --- a/scripts/kconfig/lkc.h +++ b/scripts/kconfig/lkc.h @@ -89,7 +89,7 @@ struct property *menu_add_prompt(enum prop_type type, const char *prompt, struct expr *dep); void menu_add_expr(enum prop_type type, struct expr *expr, struct expr *dep); void menu_add_symbol(enum prop_type type, struct symbol *sym, struct expr *dep); -void menu_finalize(void); +int menu_finalize(void); void menu_set_type(int type); extern struct menu rootmenu; diff --git a/scripts/kconfig/menu.c b/scripts/kconfig/menu.c index 9c079e92a9ed..99a57ce0fdc9 100644 --- a/scripts/kconfig/menu.c +++ b/scripts/kconfig/menu.c @@ -240,11 +240,12 @@ static int menu_validate_number(struct symbol *sym, struct symbol *sym2) (sym2->type == S_UNKNOWN && sym_string_valid(sym, sym2->name)); } -static void sym_check_prop(struct symbol *sym) +static int sym_check_prop(struct symbol *sym) { struct property *prop; struct symbol *sym2; char *use; + int errors = 0; for (prop = sym->prop; prop; prop = prop->next) { switch (prop->type) { @@ -258,10 +259,13 @@ static void sym_check_prop(struct symbol *sym) break; sym2 = prop_get_symbol(prop); if (sym->type == S_HEX || sym->type == S_INT) { - if (!menu_validate_number(sym, sym2)) - prop_warn(prop, - "'%s': number is invalid", - sym->name); + if (!menu_validate_number(sym, sym2)) { + fprintf(stderr, + "%s:%d: error: '%s': number is invalid\n", + prop->filename, prop->lineno, + sym->name); + errors++; + } } if (sym_is_choice(sym)) { struct menu *choice = sym_get_choice_menu(sym2); @@ -293,21 +297,28 @@ static void sym_check_prop(struct symbol *sym) prop_warn(prop, "range is only allowed " "for int or hex symbols"); if (!menu_validate_number(sym, prop->expr->left.sym) || - !menu_validate_number(sym, prop->expr->right.sym)) - prop_warn(prop, "range is invalid"); + !menu_validate_number(sym, prop->expr->right.sym)) { + fprintf(stderr, + "%s:%d: error: range is invalid\n", + prop->filename, prop->lineno); + errors++; + } break; default: ; } } + + return errors; } -static void _menu_finalize(struct menu *parent, bool inside_choice) +static int _menu_finalize(struct menu *parent, bool inside_choice) { struct menu *menu, *last_menu; struct symbol *sym; struct property *prop; struct expr *basedep, *dep, *dep2; + int errors = 0; sym = parent->sym; if (parent->list) { @@ -393,7 +404,7 @@ static void _menu_finalize(struct menu *parent, bool inside_choice) * moving on */ for (menu = parent->list; menu; menu = menu->next) - _menu_finalize(menu, sym && sym_is_choice(sym)); + errors += _menu_finalize(menu, sym && sym_is_choice(sym)); } else if (!inside_choice && sym) { /* * Automatic submenu creation. If sym is a symbol and A, B, C, @@ -461,7 +472,7 @@ static void _menu_finalize(struct menu *parent, bool inside_choice) } /* Superset, put in submenu */ next: - _menu_finalize(menu, false); + errors += _menu_finalize(menu, false); menu->parent = parent; last_menu = menu; } @@ -519,14 +530,16 @@ static void _menu_finalize(struct menu *parent, bool inside_choice) menu_warn(parent, "config symbol defined without type"); /* Check properties connected to this symbol */ - sym_check_prop(sym); + errors += sym_check_prop(sym); sym->flags |= SYMBOL_WARNED; } + + return errors; } -void menu_finalize(void) +int menu_finalize(void) { - _menu_finalize(&rootmenu, false); + return _menu_finalize(&rootmenu, false); } bool menu_has_prompt(const struct menu *menu) diff --git a/scripts/kconfig/parser.y b/scripts/kconfig/parser.y index 5fb6f07b6ad2..40ceb9908c6f 100644 --- a/scripts/kconfig/parser.y +++ b/scripts/kconfig/parser.y @@ -587,7 +587,7 @@ void conf_parse(const char *name) menu_add_prompt(P_MENU, "Main menu", NULL); } - menu_finalize(); + yynerrs += menu_finalize(); menu_for_each_entry(menu) { struct menu *child; diff --git a/scripts/kconfig/tests/err_num_non_numeric_ref/Kconfig b/scripts/kconfig/tests/err_num_non_numeric_ref/Kconfig new file mode 100644 index 000000000000..0ca7a4a460f6 --- /dev/null +++ b/scripts/kconfig/tests/err_num_non_numeric_ref/Kconfig @@ -0,0 +1,75 @@ +# SPDX-License-Identifier: GPL-2.0 +# Test non-numeric symbol references from numeric symbols + +config BOOL_SOURCE + bool + +config TRISTATE_SOURCE + tristate + +config STRING_SOURCE + string + +# Invalid int defaults + +config INT_DEFAULT_BOOL + int + default BOOL_SOURCE + +config INT_DEFAULT_TRISTATE + int + default TRISTATE_SOURCE + +config INT_DEFAULT_STRING + int + default STRING_SOURCE + +# Invalid hex defaults + +config HEX_DEFAULT_BOOL + hex + default BOOL_SOURCE + +config HEX_DEFAULT_TRISTATE + hex + default TRISTATE_SOURCE + +config HEX_DEFAULT_STRING + hex + default STRING_SOURCE + +# Invalid int ranges + +config INT_RANGE_BOOL + int + range BOOL_SOURCE 1 + +config INT_RANGE_TRISTATE + int + range TRISTATE_SOURCE 1 + +config INT_RANGE_STRING + int + range STRING_SOURCE 1 + +config INT_RANGE_MULTIPLE + int + range BOOL_SOURCE TRISTATE_SOURCE + +# Invalid hex ranges + +config HEX_RANGE_BOOL + hex + range BOOL_SOURCE 0x1 + +config HEX_RANGE_TRISTATE + hex + range TRISTATE_SOURCE 0x1 + +config HEX_RANGE_STRING + hex + range STRING_SOURCE 0x1 + +config HEX_RANGE_MULTIPLE + hex + range BOOL_SOURCE TRISTATE_SOURCE diff --git a/scripts/kconfig/tests/err_num_non_numeric_ref/__init__.py b/scripts/kconfig/tests/err_num_non_numeric_ref/__init__.py new file mode 100644 index 000000000000..9632907abead --- /dev/null +++ b/scripts/kconfig/tests/err_num_non_numeric_ref/__init__.py @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 +""" +Reject nonnumeric symbol references from int and hex properties. +""" + + +def test(conf): + assert conf.olddefconfig() == 1 + assert conf.stderr_matches('expected_stderr') diff --git a/scripts/kconfig/tests/err_num_non_numeric_ref/expected_stderr b/scripts/kconfig/tests/err_num_non_numeric_ref/expected_stderr new file mode 100644 index 000000000000..4974ba2fcd9c --- /dev/null +++ b/scripts/kconfig/tests/err_num_non_numeric_ref/expected_stderr @@ -0,0 +1,14 @@ +Kconfig:17: error: 'INT_DEFAULT_BOOL': number is invalid +Kconfig:21: error: 'INT_DEFAULT_TRISTATE': number is invalid +Kconfig:25: error: 'INT_DEFAULT_STRING': number is invalid +Kconfig:31: error: 'HEX_DEFAULT_BOOL': number is invalid +Kconfig:35: error: 'HEX_DEFAULT_TRISTATE': number is invalid +Kconfig:39: error: 'HEX_DEFAULT_STRING': number is invalid +Kconfig:45: error: range is invalid +Kconfig:49: error: range is invalid +Kconfig:53: error: range is invalid +Kconfig:57: error: range is invalid +Kconfig:63: error: range is invalid +Kconfig:67: error: range is invalid +Kconfig:71: error: range is invalid +Kconfig:75: error: range is invalid From a5e84d6f6df1fdb64c06fdb64894199aaf5ec899 Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Tue, 15 Sep 2026 22:15:05 +0100 Subject: [PATCH 12/59] kconfig: check for out-of-bounds numeric constants The Kconfig interpreter internally represents constants as strings, then attempts to parse them as 64-bit signed integers for 'int' options, and 64-bit unsigned integers for 'hex' options. However, there is currently no check that the conversion succeeds, leading to failures when the values are actually used. For example: config LARGE_INT int default 10000000000000000000 config BUGGED_INT_COMPARISON bool default y if LARGE_INT < 2 Obviously 10000000000000000000 is larger than 2, but the Kconfig interpreter will fallback to comparing the two values with strcmp() after the numeric conversion fails, causing the first character, '1', to be compared with '2', and giving the wrong result. Since none of these out-of-bounds values are used as constants anywhere in the tree, we can already make these error out. Assisted-by: LLM Signed-off-by: Julian Braha Reviewed-by: Nicolas Schier Link: https://patch.msgid.link/20260915211508.291790-3-julianbraha@gmail.com Signed-off-by: Nathan Chancellor --- scripts/kconfig/menu.c | 60 ++++++++++---- scripts/kconfig/tests/err_num_bounds/Kconfig | 79 +++++++++++++++++++ .../kconfig/tests/err_num_bounds/__init__.py | 12 +++ .../tests/err_num_bounds/expected_stderr | 10 +++ .../err_num_non_numeric_ref/expected_stderr | 30 +++---- 5 files changed, 160 insertions(+), 31 deletions(-) create mode 100644 scripts/kconfig/tests/err_num_bounds/Kconfig create mode 100644 scripts/kconfig/tests/err_num_bounds/__init__.py create mode 100644 scripts/kconfig/tests/err_num_bounds/expected_stderr diff --git a/scripts/kconfig/menu.c b/scripts/kconfig/menu.c index 99a57ce0fdc9..ede791a2fe1b 100644 --- a/scripts/kconfig/menu.c +++ b/scripts/kconfig/menu.c @@ -4,6 +4,7 @@ */ #include +#include #include #include #include @@ -234,10 +235,46 @@ void menu_add_symbol(enum prop_type type, struct symbol *sym, struct expr *dep) menu_add_prop(type, expr_alloc_symbol(sym), dep); } -static int menu_validate_number(struct symbol *sym, struct symbol *sym2) +/* Validate the sym2 value for numeric sym. */ +static int menu_validate_number(struct symbol *sym, struct symbol *sym2, + const struct property *prop) { - return sym2->type == S_INT || sym2->type == S_HEX || - (sym2->type == S_UNKNOWN && sym_string_valid(sym, sym2->name)); + const char *type_bounds; + + if (sym->type != S_INT && sym->type != S_HEX) + return 0; + + if (sym2->type == S_INT || sym2->type == S_HEX) + return 0; + + if (sym2->type != S_UNKNOWN || + !sym_string_valid(sym, sym2->name)) { + fprintf(stderr, "%s:%d: error: '%s' is an invalid value for '%s'\n", + prop->filename, prop->lineno, sym2->name, + sym_type_name(sym->type)); + return 1; + } + + errno = 0; + if (sym->type == S_INT) { + type_bounds = "64-bit signed integer"; + strtoll(sym2->name, NULL, 10); + } else { + /* hex */ + type_bounds = "64-bit unsigned integer"; + strtoull(sym2->name, NULL, 16); + } + + if (errno == ERANGE) { + fprintf(stderr, + "%s:%d: error: %s constant '%s' is outside the %s bounds\n", + prop->filename, prop->lineno, sym_type_name(sym->type), + sym2->name, type_bounds); + + return 1; + } + + return 0; } static int sym_check_prop(struct symbol *sym) @@ -259,13 +296,7 @@ static int sym_check_prop(struct symbol *sym) break; sym2 = prop_get_symbol(prop); if (sym->type == S_HEX || sym->type == S_INT) { - if (!menu_validate_number(sym, sym2)) { - fprintf(stderr, - "%s:%d: error: '%s': number is invalid\n", - prop->filename, prop->lineno, - sym->name); - errors++; - } + errors += menu_validate_number(sym, sym2, prop); } if (sym_is_choice(sym)) { struct menu *choice = sym_get_choice_menu(sym2); @@ -296,13 +327,8 @@ static int sym_check_prop(struct symbol *sym) if (sym->type != S_INT && sym->type != S_HEX) prop_warn(prop, "range is only allowed " "for int or hex symbols"); - if (!menu_validate_number(sym, prop->expr->left.sym) || - !menu_validate_number(sym, prop->expr->right.sym)) { - fprintf(stderr, - "%s:%d: error: range is invalid\n", - prop->filename, prop->lineno); - errors++; - } + errors += menu_validate_number(sym, prop->expr->left.sym, prop); + errors += menu_validate_number(sym, prop->expr->right.sym, prop); break; default: ; diff --git a/scripts/kconfig/tests/err_num_bounds/Kconfig b/scripts/kconfig/tests/err_num_bounds/Kconfig new file mode 100644 index 000000000000..c439366c03b6 --- /dev/null +++ b/scripts/kconfig/tests/err_num_bounds/Kconfig @@ -0,0 +1,79 @@ +# SPDX-License-Identifier: GPL-2.0 +# Test bounds checks for 'int' and 'hex' constants + +config INT_SOURCE + int + +config HEX_SOURCE + hex + +config BOOL_SOURCE + bool + +# Valid values at the limits of the type + +config INT_MIN + int + default -9223372036854775808 + +config INT_MAX + int + default 9223372036854775807 + +config HEX_MIN + hex + default 0x0 + +config HEX_MAX + hex + default 0xffffffffffffffff + +config INT_RANGE_LIMITS + int + range -9223372036854775808 9223372036854775807 + +config HEX_RANGE_LIMITS + hex + range 0 0xffffffffffffffff + +config INT_FROM_INT + int + default INT_SOURCE + +config HEX_FROM_HEX + hex + default HEX_SOURCE + +# Constants outside the bounds + +config INT_DEFAULT_TOO_HIGH + int + default 10000000000000000000 + +config INT_DEFAULT_TOO_LOW + int + default -9223372036854775809 + +config INT_RANGE_TOO_HIGH + int + range 0 10000000000000000000 + +config INT_RANGE_TOO_LOW + int + range -10000000000000000000 0 + +config INT_RANGE_BOTH_OUTSIDE + int + range -9223372036854775809 10000000000000000000 + +config HEX_DEFAULT_TOO_HIGH + hex + default 0x10000000000000000 + +config HEX_RANGE_TOO_HIGH + hex + range 0 0x10000000000000000 + +config HEX_RANGE_BOTH_TOO_HIGH + hex + range 0x10000000000000000 0x20000000000000000 diff --git a/scripts/kconfig/tests/err_num_bounds/__init__.py b/scripts/kconfig/tests/err_num_bounds/__init__.py new file mode 100644 index 000000000000..72ac6aa24491 --- /dev/null +++ b/scripts/kconfig/tests/err_num_bounds/__init__.py @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0 +""" +Detect constants outside the 'int' and 'hex' bounds. + +An int constant must fit in a signed 64-bit integer, and a hex constant must +fit in an unsigned 64-bit integer. +""" + + +def test(conf): + assert conf.olddefconfig() == 1 + assert conf.stderr_matches('expected_stderr') diff --git a/scripts/kconfig/tests/err_num_bounds/expected_stderr b/scripts/kconfig/tests/err_num_bounds/expected_stderr new file mode 100644 index 000000000000..3f06e13359ef --- /dev/null +++ b/scripts/kconfig/tests/err_num_bounds/expected_stderr @@ -0,0 +1,10 @@ +Kconfig:51: error: integer constant '10000000000000000000' is outside the 64-bit signed integer bounds +Kconfig:55: error: integer constant '-9223372036854775809' is outside the 64-bit signed integer bounds +Kconfig:59: error: integer constant '10000000000000000000' is outside the 64-bit signed integer bounds +Kconfig:63: error: integer constant '-10000000000000000000' is outside the 64-bit signed integer bounds +Kconfig:67: error: integer constant '-9223372036854775809' is outside the 64-bit signed integer bounds +Kconfig:67: error: integer constant '10000000000000000000' is outside the 64-bit signed integer bounds +Kconfig:71: error: hex constant '0x10000000000000000' is outside the 64-bit unsigned integer bounds +Kconfig:75: error: hex constant '0x10000000000000000' is outside the 64-bit unsigned integer bounds +Kconfig:79: error: hex constant '0x10000000000000000' is outside the 64-bit unsigned integer bounds +Kconfig:79: error: hex constant '0x20000000000000000' is outside the 64-bit unsigned integer bounds diff --git a/scripts/kconfig/tests/err_num_non_numeric_ref/expected_stderr b/scripts/kconfig/tests/err_num_non_numeric_ref/expected_stderr index 4974ba2fcd9c..005f855ecbdd 100644 --- a/scripts/kconfig/tests/err_num_non_numeric_ref/expected_stderr +++ b/scripts/kconfig/tests/err_num_non_numeric_ref/expected_stderr @@ -1,14 +1,16 @@ -Kconfig:17: error: 'INT_DEFAULT_BOOL': number is invalid -Kconfig:21: error: 'INT_DEFAULT_TRISTATE': number is invalid -Kconfig:25: error: 'INT_DEFAULT_STRING': number is invalid -Kconfig:31: error: 'HEX_DEFAULT_BOOL': number is invalid -Kconfig:35: error: 'HEX_DEFAULT_TRISTATE': number is invalid -Kconfig:39: error: 'HEX_DEFAULT_STRING': number is invalid -Kconfig:45: error: range is invalid -Kconfig:49: error: range is invalid -Kconfig:53: error: range is invalid -Kconfig:57: error: range is invalid -Kconfig:63: error: range is invalid -Kconfig:67: error: range is invalid -Kconfig:71: error: range is invalid -Kconfig:75: error: range is invalid +Kconfig:17: error: 'BOOL_SOURCE' is an invalid value for 'integer' +Kconfig:21: error: 'TRISTATE_SOURCE' is an invalid value for 'integer' +Kconfig:25: error: 'STRING_SOURCE' is an invalid value for 'integer' +Kconfig:31: error: 'BOOL_SOURCE' is an invalid value for 'hex' +Kconfig:35: error: 'TRISTATE_SOURCE' is an invalid value for 'hex' +Kconfig:39: error: 'STRING_SOURCE' is an invalid value for 'hex' +Kconfig:45: error: 'BOOL_SOURCE' is an invalid value for 'integer' +Kconfig:49: error: 'TRISTATE_SOURCE' is an invalid value for 'integer' +Kconfig:53: error: 'STRING_SOURCE' is an invalid value for 'integer' +Kconfig:57: error: 'BOOL_SOURCE' is an invalid value for 'integer' +Kconfig:57: error: 'TRISTATE_SOURCE' is an invalid value for 'integer' +Kconfig:63: error: 'BOOL_SOURCE' is an invalid value for 'hex' +Kconfig:67: error: 'TRISTATE_SOURCE' is an invalid value for 'hex' +Kconfig:71: error: 'STRING_SOURCE' is an invalid value for 'hex' +Kconfig:75: error: 'BOOL_SOURCE' is an invalid value for 'hex' +Kconfig:75: error: 'TRISTATE_SOURCE' is an invalid value for 'hex' From d13bad83d89d1022fee254c08f6e01084eb4395e Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Tue, 15 Sep 2026 22:15:06 +0100 Subject: [PATCH 13/59] kconfig: check for hex and int mismatches Using a numeric option of one type (e.g. 'int') to determine the value of a different numeric type (e.g. 'hex') currently fails silently in various ways if attempted, because the underlying string representation is naively reused. Example 1: config I int default -1 config HEX_DEFAULT_INT hex default I Here, HEX_DEFAULT_INT actually gets set to '0x-1', which is of course not a valid hex value. Example 2: config H hex default A config INT_DEFAULT_HEX int default H Here, INT_DEFAULT_HEX actually gets set to 'A', without even converting into the base-10 equivalent of 10. This value, 'A', is otherwise a rejected int value if entered in the frontend, or read in from an existing .config file. These int-hex mismatches currently do not appear anywhere in the tree, so it is already safe to make these error out. Assisted-by: LLM Signed-off-by: Julian Braha Reviewed-by: Nicolas Schier Link: https://patch.msgid.link/20260915211508.291790-4-julianbraha@gmail.com Signed-off-by: Nathan Chancellor --- scripts/kconfig/menu.c | 2 +- .../kconfig/tests/err_num_mismatch/Kconfig | 38 +++++++++++++++++++ .../tests/err_num_mismatch/__init__.py | 9 +++++ .../tests/err_num_mismatch/expected_stderr | 4 ++ 4 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 scripts/kconfig/tests/err_num_mismatch/Kconfig create mode 100644 scripts/kconfig/tests/err_num_mismatch/__init__.py create mode 100644 scripts/kconfig/tests/err_num_mismatch/expected_stderr diff --git a/scripts/kconfig/menu.c b/scripts/kconfig/menu.c index ede791a2fe1b..2d8b0c65ce1e 100644 --- a/scripts/kconfig/menu.c +++ b/scripts/kconfig/menu.c @@ -244,7 +244,7 @@ static int menu_validate_number(struct symbol *sym, struct symbol *sym2, if (sym->type != S_INT && sym->type != S_HEX) return 0; - if (sym2->type == S_INT || sym2->type == S_HEX) + if (sym2->type == sym->type) return 0; if (sym2->type != S_UNKNOWN || diff --git a/scripts/kconfig/tests/err_num_mismatch/Kconfig b/scripts/kconfig/tests/err_num_mismatch/Kconfig new file mode 100644 index 000000000000..8406f3bb6419 --- /dev/null +++ b/scripts/kconfig/tests/err_num_mismatch/Kconfig @@ -0,0 +1,38 @@ +# SPDX-License-Identifier: GPL-2.0 +# Test 'int' and 'hex' symbols that reference each other + +config INT_SOURCE + int + +config HEX_SOURCE + hex + +# 'hex' reference from 'int' + +config INT_DEFAULT_HEX + int + default HEX_SOURCE + +config INT_RANGE_HEX + int + range HEX_SOURCE 1 + +# A hex symbol must not reference an int symbol + +config HEX_DEFAULT_INT + hex + default INT_SOURCE + +config HEX_RANGE_INT + hex + range INT_SOURCE 0x1 + +# Referencing the same type is valid + +config INT_FROM_INT + int + default INT_SOURCE + +config HEX_FROM_HEX + hex + range 0 HEX_SOURCE diff --git a/scripts/kconfig/tests/err_num_mismatch/__init__.py b/scripts/kconfig/tests/err_num_mismatch/__init__.py new file mode 100644 index 000000000000..275f2a6e9a5b --- /dev/null +++ b/scripts/kconfig/tests/err_num_mismatch/__init__.py @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 +""" +Reject direct references ('default' or 'range') between int and hex options. +""" + + +def test(conf): + assert conf.olddefconfig() == 1 + assert conf.stderr_matches('expected_stderr') diff --git a/scripts/kconfig/tests/err_num_mismatch/expected_stderr b/scripts/kconfig/tests/err_num_mismatch/expected_stderr new file mode 100644 index 000000000000..587c34467ae6 --- /dev/null +++ b/scripts/kconfig/tests/err_num_mismatch/expected_stderr @@ -0,0 +1,4 @@ +Kconfig:14: error: 'HEX_SOURCE' is an invalid value for 'integer' +Kconfig:18: error: 'HEX_SOURCE' is an invalid value for 'integer' +Kconfig:24: error: 'INT_SOURCE' is an invalid value for 'hex' +Kconfig:28: error: 'INT_SOURCE' is an invalid value for 'hex' From 54f11924e4854177b357af499ebc447dc8c3c590 Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Tue, 15 Sep 2026 22:15:07 +0100 Subject: [PATCH 14/59] kconfig: prevent out-of-bounds user input for numeric options Currently, user input of out-of-bounds values for 'int' and 'hex' options is possible, leading to later silent failures in Kconfig if that value is used in a comparison, or a warning by GCC or error by Clang if used in the C code. Let's factor out the out-of-bounds check on constants, so that it can be reused for checking user input. The frontend will now reject an attempted user input of an out-of-bounds numeric value, similarly to how a value outside the active 'range' already does. For migration of existing configurations, a .config file with an out-of-bounds numeric value will be allowed for now, but will warn the user when read in by confdata.c Assisted-by: LLM Signed-off-by: Julian Braha Reviewed-by: Nicolas Schier Link: https://patch.msgid.link/20260915211508.291790-5-julianbraha@gmail.com Signed-off-by: Nathan Chancellor --- scripts/kconfig/confdata.c | 6 +++++ scripts/kconfig/lkc_proto.h | 1 + scripts/kconfig/menu.c | 19 +++----------- scripts/kconfig/symbol.c | 20 +++++++++++++++ .../tests/err_num_bounds/expected_stderr | 20 +++++++-------- scripts/kconfig/tests/warn_num_bounds/Kconfig | 24 ++++++++++++++++++ .../kconfig/tests/warn_num_bounds/__init__.py | 25 +++++++++++++++++++ scripts/kconfig/tests/warn_num_bounds/config | 7 ++++++ .../tests/warn_num_bounds/expected_config | 11 ++++++++ .../warn_num_bounds/expected_config_stderr | 3 +++ .../warn_num_bounds/expected_frontend_config | 11 ++++++++ .../warn_num_bounds/expected_frontend_stderr | 0 12 files changed, 121 insertions(+), 26 deletions(-) create mode 100644 scripts/kconfig/tests/warn_num_bounds/Kconfig create mode 100644 scripts/kconfig/tests/warn_num_bounds/__init__.py create mode 100644 scripts/kconfig/tests/warn_num_bounds/config create mode 100644 scripts/kconfig/tests/warn_num_bounds/expected_config create mode 100644 scripts/kconfig/tests/warn_num_bounds/expected_config_stderr create mode 100644 scripts/kconfig/tests/warn_num_bounds/expected_frontend_config create mode 100644 scripts/kconfig/tests/warn_num_bounds/expected_frontend_stderr diff --git a/scripts/kconfig/confdata.c b/scripts/kconfig/confdata.c index 4234a51d16fd..2227d89d6328 100644 --- a/scripts/kconfig/confdata.c +++ b/scripts/kconfig/confdata.c @@ -354,6 +354,12 @@ static int conf_set_sym_val(struct symbol *sym, int def, int def_flags, char *p) case S_INT: case S_HEX: if (sym_string_valid(sym, p)) { + if (def != S_DEF_AUTO && + !sym_string_check_bounds(sym, p)) + /* hex uses 64-bit unsigned integer */ + conf_warning("value '%s' for %s is outside the 64-bit %s integer bounds", + p, sym->name, + sym->type == S_INT ? "signed" : "unsigned"); sym->def[def].val = xstrdup(p); sym->flags |= def_flags; } else { diff --git a/scripts/kconfig/lkc_proto.h b/scripts/kconfig/lkc_proto.h index 8914b4e8f2a8..8b436c87ba4a 100644 --- a/scripts/kconfig/lkc_proto.h +++ b/scripts/kconfig/lkc_proto.h @@ -31,6 +31,7 @@ bool sym_set_tristate_value(struct symbol *sym,tristate tri); void choice_set_value(struct menu *choice, struct symbol *sym); tristate sym_toggle_tristate_value(struct symbol *sym); bool sym_string_valid(struct symbol *sym, const char *newval); +bool sym_string_check_bounds(struct symbol *sym, const char *str); bool sym_string_within_range(struct symbol *sym, const char *str); bool sym_set_string_value(struct symbol *sym, const char *newval); bool sym_is_changeable(const struct symbol *sym); diff --git a/scripts/kconfig/menu.c b/scripts/kconfig/menu.c index 2d8b0c65ce1e..6b9ef738ee71 100644 --- a/scripts/kconfig/menu.c +++ b/scripts/kconfig/menu.c @@ -4,7 +4,6 @@ */ #include -#include #include #include #include @@ -239,8 +238,6 @@ void menu_add_symbol(enum prop_type type, struct symbol *sym, struct expr *dep) static int menu_validate_number(struct symbol *sym, struct symbol *sym2, const struct property *prop) { - const char *type_bounds; - if (sym->type != S_INT && sym->type != S_HEX) return 0; @@ -255,21 +252,11 @@ static int menu_validate_number(struct symbol *sym, struct symbol *sym2, return 1; } - errno = 0; - if (sym->type == S_INT) { - type_bounds = "64-bit signed integer"; - strtoll(sym2->name, NULL, 10); - } else { - /* hex */ - type_bounds = "64-bit unsigned integer"; - strtoull(sym2->name, NULL, 16); - } - - if (errno == ERANGE) { + if (!sym_string_check_bounds(sym, sym2->name)) { fprintf(stderr, - "%s:%d: error: %s constant '%s' is outside the %s bounds\n", + "%s:%d: error: %s constant '%s' is outside the 64-bit %s bounds\n", prop->filename, prop->lineno, sym_type_name(sym->type), - sym2->name, type_bounds); + sym2->name, sym->type == S_INT ? "signed" : "unsigned"); return 1; } diff --git a/scripts/kconfig/symbol.c b/scripts/kconfig/symbol.c index 01aa58d92c3b..8ccd9e7e2cf0 100644 --- a/scripts/kconfig/symbol.c +++ b/scripts/kconfig/symbol.c @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -726,6 +727,21 @@ bool sym_string_valid(struct symbol *sym, const char *str) } } +bool sym_string_check_bounds(struct symbol *sym, const char *str) +{ + errno = 0; + + if (sym->type == S_INT) + strtoll(str, NULL, 10); + else if (sym->type == S_HEX) + strtoull(str, NULL, 16); + else + /* string */ + return true; + + return errno != ERANGE; +} + bool sym_string_within_range(struct symbol *sym, const char *str) { struct property *prop; @@ -737,6 +753,8 @@ bool sym_string_within_range(struct symbol *sym, const char *str) case S_INT: if (!sym_string_valid(sym, str)) return false; + if (!sym_string_check_bounds(sym, str)) + return false; prop = sym_get_range_prop(sym); if (!prop) return true; @@ -746,6 +764,8 @@ bool sym_string_within_range(struct symbol *sym, const char *str) case S_HEX: if (!sym_string_valid(sym, str)) return false; + if (!sym_string_check_bounds(sym, str)) + return false; prop = sym_get_range_prop(sym); if (!prop) return true; diff --git a/scripts/kconfig/tests/err_num_bounds/expected_stderr b/scripts/kconfig/tests/err_num_bounds/expected_stderr index 3f06e13359ef..5f89cf4afff8 100644 --- a/scripts/kconfig/tests/err_num_bounds/expected_stderr +++ b/scripts/kconfig/tests/err_num_bounds/expected_stderr @@ -1,10 +1,10 @@ -Kconfig:51: error: integer constant '10000000000000000000' is outside the 64-bit signed integer bounds -Kconfig:55: error: integer constant '-9223372036854775809' is outside the 64-bit signed integer bounds -Kconfig:59: error: integer constant '10000000000000000000' is outside the 64-bit signed integer bounds -Kconfig:63: error: integer constant '-10000000000000000000' is outside the 64-bit signed integer bounds -Kconfig:67: error: integer constant '-9223372036854775809' is outside the 64-bit signed integer bounds -Kconfig:67: error: integer constant '10000000000000000000' is outside the 64-bit signed integer bounds -Kconfig:71: error: hex constant '0x10000000000000000' is outside the 64-bit unsigned integer bounds -Kconfig:75: error: hex constant '0x10000000000000000' is outside the 64-bit unsigned integer bounds -Kconfig:79: error: hex constant '0x10000000000000000' is outside the 64-bit unsigned integer bounds -Kconfig:79: error: hex constant '0x20000000000000000' is outside the 64-bit unsigned integer bounds +Kconfig:51: error: integer constant '10000000000000000000' is outside the 64-bit signed bounds +Kconfig:55: error: integer constant '-9223372036854775809' is outside the 64-bit signed bounds +Kconfig:59: error: integer constant '10000000000000000000' is outside the 64-bit signed bounds +Kconfig:63: error: integer constant '-10000000000000000000' is outside the 64-bit signed bounds +Kconfig:67: error: integer constant '-9223372036854775809' is outside the 64-bit signed bounds +Kconfig:67: error: integer constant '10000000000000000000' is outside the 64-bit signed bounds +Kconfig:71: error: hex constant '0x10000000000000000' is outside the 64-bit unsigned bounds +Kconfig:75: error: hex constant '0x10000000000000000' is outside the 64-bit unsigned bounds +Kconfig:79: error: hex constant '0x10000000000000000' is outside the 64-bit unsigned bounds +Kconfig:79: error: hex constant '0x20000000000000000' is outside the 64-bit unsigned bounds diff --git a/scripts/kconfig/tests/warn_num_bounds/Kconfig b/scripts/kconfig/tests/warn_num_bounds/Kconfig new file mode 100644 index 000000000000..a810225a58a3 --- /dev/null +++ b/scripts/kconfig/tests/warn_num_bounds/Kconfig @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: GPL-2.0 + +mainmenu "Numeric bounds test" + +config INT_TOO_LOW + int "Integer below its type bounds" + +config INT_TOO_HIGH + int "Integer above its type bounds" + +config HEX_TOO_HIGH + hex "Hex value above its type bounds" + +config INT_MIN + int "Minimum valid integer" + +config INT_MAX + int "Maximum valid integer" + +config HEX_MIN + hex "Minimum valid hex value" + +config HEX_MAX + hex "Maximum valid hex value" diff --git a/scripts/kconfig/tests/warn_num_bounds/__init__.py b/scripts/kconfig/tests/warn_num_bounds/__init__.py new file mode 100644 index 000000000000..db7518258217 --- /dev/null +++ b/scripts/kconfig/tests/warn_num_bounds/__init__.py @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: GPL-2.0 +"""Test user values outside the numeric type bounds.""" + + +def test(conf): + in_keys = ( + '-9223372036854775809\n' + '-1\n' + '9223372036854775808\n' + '1\n' + '0x10000000000000000\n' + '0x1\n' + '-9223372036854775808\n' + '9223372036854775807\n' + '0x0\n' + '0xffffffffffffffff\n' + ) + + assert conf.oldaskconfig(in_keys=in_keys) == 0 + assert conf.stderr_matches('expected_frontend_stderr') + assert conf.config_matches('expected_frontend_config') + + assert conf.olddefconfig('config') == 0 + assert conf.stderr_matches('expected_config_stderr') + assert conf.config_matches('expected_config') diff --git a/scripts/kconfig/tests/warn_num_bounds/config b/scripts/kconfig/tests/warn_num_bounds/config new file mode 100644 index 000000000000..74bf263f99bf --- /dev/null +++ b/scripts/kconfig/tests/warn_num_bounds/config @@ -0,0 +1,7 @@ +CONFIG_INT_TOO_LOW=-9223372036854775809 +CONFIG_INT_TOO_HIGH=9223372036854775808 +CONFIG_HEX_TOO_HIGH=0x10000000000000000 +CONFIG_INT_MIN=-9223372036854775808 +CONFIG_INT_MAX=9223372036854775807 +CONFIG_HEX_MIN=0x0 +CONFIG_HEX_MAX=0xffffffffffffffff diff --git a/scripts/kconfig/tests/warn_num_bounds/expected_config b/scripts/kconfig/tests/warn_num_bounds/expected_config new file mode 100644 index 000000000000..8b1aef612bc6 --- /dev/null +++ b/scripts/kconfig/tests/warn_num_bounds/expected_config @@ -0,0 +1,11 @@ +# +# Automatically generated file; DO NOT EDIT. +# Numeric bounds test +# +CONFIG_INT_TOO_LOW=-9223372036854775809 +CONFIG_INT_TOO_HIGH=9223372036854775808 +CONFIG_HEX_TOO_HIGH=0x10000000000000000 +CONFIG_INT_MIN=-9223372036854775808 +CONFIG_INT_MAX=9223372036854775807 +CONFIG_HEX_MIN=0x0 +CONFIG_HEX_MAX=0xffffffffffffffff diff --git a/scripts/kconfig/tests/warn_num_bounds/expected_config_stderr b/scripts/kconfig/tests/warn_num_bounds/expected_config_stderr new file mode 100644 index 000000000000..be2ed7fbf648 --- /dev/null +++ b/scripts/kconfig/tests/warn_num_bounds/expected_config_stderr @@ -0,0 +1,3 @@ +.config:1:warning: value '-9223372036854775809' for INT_TOO_LOW is outside the 64-bit signed integer bounds +.config:2:warning: value '9223372036854775808' for INT_TOO_HIGH is outside the 64-bit signed integer bounds +.config:3:warning: value '0x10000000000000000' for HEX_TOO_HIGH is outside the 64-bit unsigned integer bounds diff --git a/scripts/kconfig/tests/warn_num_bounds/expected_frontend_config b/scripts/kconfig/tests/warn_num_bounds/expected_frontend_config new file mode 100644 index 000000000000..a7e842517026 --- /dev/null +++ b/scripts/kconfig/tests/warn_num_bounds/expected_frontend_config @@ -0,0 +1,11 @@ +# +# Automatically generated file; DO NOT EDIT. +# Numeric bounds test +# +CONFIG_INT_TOO_LOW=-1 +CONFIG_INT_TOO_HIGH=1 +CONFIG_HEX_TOO_HIGH=0x1 +CONFIG_INT_MIN=-9223372036854775808 +CONFIG_INT_MAX=9223372036854775807 +CONFIG_HEX_MIN=0x0 +CONFIG_HEX_MAX=0xffffffffffffffff diff --git a/scripts/kconfig/tests/warn_num_bounds/expected_frontend_stderr b/scripts/kconfig/tests/warn_num_bounds/expected_frontend_stderr new file mode 100644 index 000000000000..e69de29bb2d1 From 9b8719973ee2c5ec23aab9b958e5c6fbdf352f90 Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Tue, 15 Sep 2026 22:15:08 +0100 Subject: [PATCH 15/59] kconfig: use unsigned integers for hex range checks While 'hex' values should use the bounds of 64-bit unsigned integers, the range values are treated as signed (checks use strtoll). For example: config HEX_RANGE hex range 0 0xfffffffffffffffe default 0xffffffffffffffff the default value should adjust down to the upper bound of 0xfffffffffffffffe, but since the check incorrectly parses both as LLONG_MAX, the value stays at the out-of-range default of 0xffffffffffffffff. To fix this, let's use a union of signed and unsigned integers to represent both 'int' and 'hex' range values, then use both strtoll and strtoull where appropriate. Assisted-by: LLM Signed-off-by: Julian Braha Reviewed-by: Nicolas Schier Link: https://patch.msgid.link/20260915211508.291790-6-julianbraha@gmail.com Signed-off-by: Nathan Chancellor --- scripts/kconfig/symbol.c | 41 ++++++++++++++----- .../kconfig/tests/warn_changed_input/Kconfig | 5 +++ .../tests/warn_changed_input/expected_config | 1 + 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/scripts/kconfig/symbol.c b/scripts/kconfig/symbol.c index 8ccd9e7e2cf0..dcb4b45e674b 100644 --- a/scripts/kconfig/symbol.c +++ b/scripts/kconfig/symbol.c @@ -142,8 +142,15 @@ struct property *sym_get_range_prop(struct symbol *sym) return NULL; } -static long long sym_get_range_val(struct symbol *sym, int base) +union range_value { + long long s; + unsigned long long u; +}; + +static union range_value sym_get_range_val(struct symbol *sym, int base) { + union range_value val; + sym_calc_value(sym); switch (sym->type) { case S_INT: @@ -155,7 +162,12 @@ static long long sym_get_range_val(struct symbol *sym, int base) default: break; } - return strtoll(sym->curr.val, NULL, base); + if (base == 10) + val.s = strtoll(sym->curr.val, NULL, base); + else + /* HEX */ + val.u = strtoull(sym->curr.val, NULL, base); + return val; } /* @@ -168,7 +180,7 @@ static struct symbol *sym_get_near_range_bound(struct symbol *sym, struct property *prop; struct symbol *range_sym; int base; - long long val, val2; + union range_value val, val2; switch (sym->type) { case S_INT: @@ -183,13 +195,19 @@ static struct symbol *sym_get_near_range_bound(struct symbol *sym, prop = sym_get_range_prop(sym); if (!prop) return NULL; - val = strtoll(value, NULL, base); + val = sym_get_range_val(sym, base); range_sym = prop->expr->left.sym; val2 = sym_get_range_val(range_sym, base); - if (val >= val2) { + + if (base == 10 && val.s >= val2.s) { range_sym = prop->expr->right.sym; val2 = sym_get_range_val(range_sym, base); - if (val <= val2) + if (val.s <= val2.s) + return NULL; + } else if (base == 16 && val.u >= val2.u) { + range_sym = prop->expr->right.sym; + val2 = sym_get_range_val(range_sym, base); + if (val.u <= val2.u) return NULL; } @@ -746,6 +764,7 @@ bool sym_string_within_range(struct symbol *sym, const char *str) { struct property *prop; long long val; + unsigned long long uval; switch (sym->type) { case S_STRING: @@ -759,8 +778,8 @@ bool sym_string_within_range(struct symbol *sym, const char *str) if (!prop) return true; val = strtoll(str, NULL, 10); - return val >= sym_get_range_val(prop->expr->left.sym, 10) && - val <= sym_get_range_val(prop->expr->right.sym, 10); + return val >= sym_get_range_val(prop->expr->left.sym, 10).s && + val <= sym_get_range_val(prop->expr->right.sym, 10).s; case S_HEX: if (!sym_string_valid(sym, str)) return false; @@ -769,9 +788,9 @@ bool sym_string_within_range(struct symbol *sym, const char *str) prop = sym_get_range_prop(sym); if (!prop) return true; - val = strtoll(str, NULL, 16); - return val >= sym_get_range_val(prop->expr->left.sym, 16) && - val <= sym_get_range_val(prop->expr->right.sym, 16); + uval = strtoull(str, NULL, 16); + return uval >= sym_get_range_val(prop->expr->left.sym, 16).u && + uval <= sym_get_range_val(prop->expr->right.sym, 16).u; case S_BOOLEAN: case S_TRISTATE: switch (str[0]) { diff --git a/scripts/kconfig/tests/warn_changed_input/Kconfig b/scripts/kconfig/tests/warn_changed_input/Kconfig index 69845e2f3fb3..df4183a08420 100644 --- a/scripts/kconfig/tests/warn_changed_input/Kconfig +++ b/scripts/kconfig/tests/warn_changed_input/Kconfig @@ -26,6 +26,11 @@ config NUM Kconfig resolves it to the constrained in-range value. The warning should report that adjustment. +config HEX_RANGE + hex + range 0 0xfffffffffffffffe + default 0xffffffffffffffff + config DUP bool "DUP" depends on DEP diff --git a/scripts/kconfig/tests/warn_changed_input/expected_config b/scripts/kconfig/tests/warn_changed_input/expected_config index fe8bbec66c53..6a8d98a69b7e 100644 --- a/scripts/kconfig/tests/warn_changed_input/expected_config +++ b/scripts/kconfig/tests/warn_changed_input/expected_config @@ -4,3 +4,4 @@ # # CONFIG_DEP is not set CONFIG_NUM=20 +CONFIG_HEX_RANGE=0xfffffffffffffffe From 35abc3cd8ab65dd7d07d4fc48a0d7048ce47d055 Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Fri, 7 Aug 2026 18:50:41 +0100 Subject: [PATCH 16/59] tee: remove TZMEM_MODE_GENERIC 'select' does not work on config options in a 'choice', so currently it is possible to enable QCOMTEE without QCOM_TZMEM_MODE_SHMBRIDGE, even though this is needed at runtime. There are no users of the generic allocator option, QCOM_TZMEM_MODE_GENERIC, so let's remove it. Then, we can remove the containing choice..endchoice, which allows the 'select' to work as intended. Suggested-by: Arnd Bergmann Signed-off-by: Julian Braha Reviewed-by: Arnd Bergmann Reviewed-by: Nathan Chancellor Reviewed-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260807175041.3299349-1-julianbraha@gmail.com Signed-off-by: Nathan Chancellor --- drivers/firmware/qcom/Kconfig | 26 +++++--------------------- drivers/firmware/qcom/qcom_tzmem.c | 4 ++-- 2 files changed, 7 insertions(+), 23 deletions(-) diff --git a/drivers/firmware/qcom/Kconfig b/drivers/firmware/qcom/Kconfig index c7f8413ab996..95b968d88dc3 100644 --- a/drivers/firmware/qcom/Kconfig +++ b/drivers/firmware/qcom/Kconfig @@ -34,33 +34,17 @@ config QCOM_TZMEM tristate select GENERIC_ALLOCATOR -choice - prompt "TrustZone interface memory allocator mode" - depends on QCOM_TZMEM - default QCOM_TZMEM_MODE_GENERIC - help - Selects the mode of the memory allocator providing memory buffers of - suitable format for sharing with the TrustZone. If in doubt, select - 'Generic'. - -config QCOM_TZMEM_MODE_GENERIC - bool "Generic" - help - Use the generic allocator mode. The memory is page-aligned, non-cachable - and physically contiguous. - config QCOM_TZMEM_MODE_SHMBRIDGE - bool "SHM Bridge" + bool "TrustZone interface memory allocator: SHM Bridge" + depends on QCOM_TZMEM help - Use Qualcomm Shared Memory Bridge. The memory has the same alignment as - in the 'Generic' allocator but is also explicitly marked as an SHM Bridge - buffer. + Use Qualcomm Shared Memory Bridge as memory allocator. The memory has the + same alignment as in the 'Generic' allocator, which is used when this option + is disabled, but is also explicitly marked as an SHM Bridge buffer. With this selected, all buffers passed to the TrustZone must be allocated using the TZMem allocator or else the TrustZone will refuse to use them. -endchoice - config QCOM_QSEECOM bool "Qualcomm QSEECOM interface driver" depends on QCOM_SCM=y diff --git a/drivers/firmware/qcom/qcom_tzmem.c b/drivers/firmware/qcom/qcom_tzmem.c index 0fd9581275f1..510474902c3a 100644 --- a/drivers/firmware/qcom/qcom_tzmem.c +++ b/drivers/firmware/qcom/qcom_tzmem.c @@ -50,7 +50,7 @@ static struct device *qcom_tzmem_dev; static RADIX_TREE(qcom_tzmem_chunks, GFP_ATOMIC); static DEFINE_SPINLOCK(qcom_tzmem_chunks_lock); -#if IS_ENABLED(CONFIG_QCOM_TZMEM_MODE_GENERIC) +#ifndef CONFIG_QCOM_TZMEM_MODE_SHMBRIDGE static int qcom_tzmem_init(void) { @@ -67,7 +67,7 @@ static void qcom_tzmem_cleanup_area(struct qcom_tzmem_area *area) } -#elif IS_ENABLED(CONFIG_QCOM_TZMEM_MODE_SHMBRIDGE) +#else #include #include From 47ef078cf9ad37a35b20094a824ec09b845532ff Mon Sep 17 00:00:00 2001 From: Nicolas Schier Date: Tue, 4 Aug 2026 14:45:32 +0200 Subject: [PATCH 17/59] kconfig: Warn on dead select or imply for choice values Update the internal kconfig check to print a warning on 'select' and 'imply' statements that target on choice values. Julian Braha reported that kconfig's 'select' and 'imply' do not work for choice values but have no effect all. Show a warning instead of error-out to allow kconfig to check the whole kconfig rule set. Reported-by: Julian Braha Closes: https://lore.kernel.org/linux-kbuild/20260715220149.48700-1-julianbraha@gmail.com/ # [1] Suggested-by: Kees Cook Suggested-by: Jani Nikula Signed-off-by: Nicolas Schier Reviewed-by: Julian Braha Tested-by: Julian Braha Link: https://patch.msgid.link/20260804-kconfig-warn-on-dead-select-for-choice-value-v1-1-e5c9a274d291@kernel.org Signed-off-by: Nathan Chancellor --- scripts/kconfig/menu.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/kconfig/menu.c b/scripts/kconfig/menu.c index 6b9ef738ee71..bda3e2650216 100644 --- a/scripts/kconfig/menu.c +++ b/scripts/kconfig/menu.c @@ -309,6 +309,10 @@ static int sym_check_prop(struct symbol *sym) "'%s' has wrong type. '%s' only " "accept arguments of bool and " "tristate type", sym2->name, use); + if (sym_is_choice_value(sym2)) + prop_warn(prop, + "config symbol '%s' uses %s for '%s', but '%s' is a choice value", + sym->name, use, sym2->name, sym2->name); break; case P_RANGE: if (sym->type != S_INT && sym->type != S_HEX) From 36d4a11b56aa98da4b4652a2e34d229ffcf36070 Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Thu, 17 Sep 2026 17:29:53 -0700 Subject: [PATCH 18/59] kbuild: Remove alignment on .modinfo section Commit d50f21091358 ("kbuild: align modinfo section for Secureboot Authenticode EDK2 compat") aligned .modinfo because it appeared in the x86_64 compressed kernel image after commit 3e86e4d74c04 ("kbuild: keep .modinfo section in vmlinux.unstripped"), breaking boot. Commit 8678591b4746 ("kbuild: Split .modinfo out from ELF_DETAILS") properly addresses .modinfo appearing in unexpected contexts like compressed images by moving it out of ELF_DETAILS and explicitly specifying its location in every architecture's vmlinux linker script or discarding it where it may appear elsewhere. Effectively revert commit d50f21091358 ("kbuild: align modinfo section for Secureboot Authenticode EDK2 compat") and its follow up fix, commit a26a6c93edfe ("kbuild: Strip trailing padding bytes from modules.builtin.modinfo"), as they are no longer necessary in the current tree. This simplifies future cleanups. Reviewed-by: Nicolas Schier Link: https://patch.msgid.link/20260918005142.GA1585590@ax162/ Signed-off-by: Nathan Chancellor --- include/asm-generic/vmlinux.lds.h | 2 +- scripts/Makefile.vmlinux | 10 ---------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h index b2988aa12f66..26201821ea7f 100644 --- a/include/asm-generic/vmlinux.lds.h +++ b/include/asm-generic/vmlinux.lds.h @@ -855,7 +855,7 @@ KLP_SYMID #define MODINFO \ - .modinfo : { *(.modinfo) . = ALIGN(8); } + .modinfo : { *(.modinfo) } #ifdef CONFIG_GENERIC_BUG #define BUG_TABLE \ diff --git a/scripts/Makefile.vmlinux b/scripts/Makefile.vmlinux index fcae1e432d9a..6833b517cacb 100644 --- a/scripts/Makefile.vmlinux +++ b/scripts/Makefile.vmlinux @@ -102,18 +102,8 @@ vmlinux: vmlinux.unstripped FORCE # modules.builtin.modinfo # --------------------------------------------------------------------------- -# .modinfo in vmlinux.unstripped is aligned to 8 bytes for compatibility with -# tools that expect vmlinux to have sufficiently aligned sections but the -# additional bytes used for padding .modinfo to satisfy this requirement break -# certain versions of kmod with -# -# depmod: ERROR: kmod_builtin_iter_next: unexpected string without modname prefix -# -# Strip the trailing padding bytes after extracting .modinfo to comply with -# what kmod expects to parse. quiet_cmd_modules_builtin_modinfo = GEN $@ cmd_modules_builtin_modinfo = $(cmd_objcopy); \ - sed -i 's/\x00\+$$/\x00/g' $@; \ chmod -x $@ OBJCOPYFLAGS_modules.builtin.modinfo := -j .modinfo -O binary From 9f01cd3a324390b235c67ae4ca3d35085369ebdd Mon Sep 17 00:00:00 2001 From: Dmitrii Tulnov Date: Sat, 19 Sep 2026 00:34:30 +0300 Subject: [PATCH 19/59] kconfig: warn about malformed KCONFIG_PROBABILITY values randconfig checks the numeric range of each probability but does not validate where strtol() stops. For example, KCONFIG_PROBABILITY=50% is accepted as 50:0:0: the '%' is parsed repeatedly as zero. For the documented value 50, tristate y/m/n probabilities are 25%/25%/50%. With 50%, both y/m probabilities silently become zero, reducing the coverage of random configuration builds. Empty fields and extra fields are also accepted. Warn when the value does not follow the documented decimal format. For malformed inputs whose parsed probabilities are in range, preserve the existing interpretation unless KCONFIG_WERROR is set. In that case, exit with an error before writing the configuration. Leading whitespace and signs are accepted by strtol(), but also trigger the warning because they are outside the documented format. Keep the strtol() result as long until the range check. This rejects out-of-range values that narrowing to int previously made valid, such as 4294967296 becoming zero on a 64-bit host, regardless of KCONFIG_WERROR. Add regression tests for one warning per malformed input, preserved configurations, KCONFIG_WERROR, out-of-range values, the supported probability formats, and the documented empty-value default. Fixes: e43956e60769 ("kconfig: implement KCONFIG_PROBABILITY for randconfig") Assisted-by: LLM Signed-off-by: Dmitrii Tulnov Tested-by: Julian Braha Reviewed-by: Julian Braha Link: https://patch.msgid.link/20260918213430.3-1-tulnov.dl@gmail.com Signed-off-by: Nathan Chancellor --- scripts/kconfig/conf.c | 16 ++- scripts/kconfig/tests/conftest.py | 6 + .../tests/randconfig_probability/Kconfig | 12 ++ .../tests/randconfig_probability/__init__.py | 130 ++++++++++++++++++ 4 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 scripts/kconfig/tests/randconfig_probability/Kconfig create mode 100644 scripts/kconfig/tests/randconfig_probability/__init__.py diff --git a/scripts/kconfig/conf.c b/scripts/kconfig/conf.c index fe8ba09b0039..ca6d9d735922 100644 --- a/scripts/kconfig/conf.c +++ b/scripts/kconfig/conf.c @@ -186,12 +186,23 @@ static void conf_set_all_new_symbols(enum conf_def_mode mode) if (mode == def_random) { int n, p[3]; + bool warned = false; char *env = getenv("KCONFIG_PROBABILITY"); n = 0; while (env && *env) { char *endp; - int tmp = strtol(env, &endp, 10); + long tmp = strtol(env, &endp, 10); + + if (!isdigit((unsigned char)*env) || + (*endp && *endp != ':') || + (*endp == ':' && (!endp[1] || n == 2))) { + if (!warned) { + fprintf(stderr, + "warning: KCONFIG_PROBABILITY has malformed format\n"); + warned = true; + } + } if (tmp >= 0 && tmp <= 100) { p[n++] = tmp; @@ -227,6 +238,9 @@ static void conf_set_all_new_symbols(enum conf_def_mode mode) perror("KCONFIG_PROBABILITY"); exit(1); } + + if (warned && getenv("KCONFIG_WERROR")) + exit(1); } menu_for_each_entry(menu) { diff --git a/scripts/kconfig/tests/conftest.py b/scripts/kconfig/tests/conftest.py index 66f95e4ed58c..2263bd2a3b3a 100644 --- a/scripts/kconfig/tests/conftest.py +++ b/scripts/kconfig/tests/conftest.py @@ -312,6 +312,12 @@ class Conf: return self._matches('stderr', expected) +@pytest.fixture(autouse=True) +def clear_werror(monkeypatch): + # extra_env can set strict mode, but cannot remove an inherited flag. + monkeypatch.delenv('KCONFIG_WERROR', raising=False) + + @pytest.fixture(scope="module") def conf(request): """Create a Conf instance and provide it to test functions.""" diff --git a/scripts/kconfig/tests/randconfig_probability/Kconfig b/scripts/kconfig/tests/randconfig_probability/Kconfig new file mode 100644 index 000000000000..84f4e5fcc51e --- /dev/null +++ b/scripts/kconfig/tests/randconfig_probability/Kconfig @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config MODULES + bool + default y + modules + +config BOOL + bool "Bool" + +config TRI + tristate "Tristate" diff --git a/scripts/kconfig/tests/randconfig_probability/__init__.py b/scripts/kconfig/tests/randconfig_probability/__init__.py new file mode 100644 index 000000000000..e6e5dda10ad9 --- /dev/null +++ b/scripts/kconfig/tests/randconfig_probability/__init__.py @@ -0,0 +1,130 @@ +# SPDX-License-Identifier: GPL-2.0-only +"""Validate KCONFIG_PROBABILITY without changing the supported distributions.""" + + +def test_malformed_warns(conf): + probabilities = [ + 'invalid', ' ', '50%', '50 ', '0x32', '10 20', '10:20x', + '10:20:invalid', '10:20:30x', + ':50', '50:', '10::20', '10:20:', '10:20:30:', '10:20:30:40', + '-0', '+0', '+', '-', '+100:0', ' \t100:0', '0: \t100:0', + ] + for probability in probabilities: + assert conf._run_conf('--randconfig', extra_env={ + 'KCONFIG_PROBABILITY': probability, + 'KCONFIG_SEED': '0', + }) == 0, repr(probability) + assert ('warning: KCONFIG_PROBABILITY has malformed format' in + conf.stderr), repr(probability) + assert conf.stderr.count('warning:') == 1, repr(probability) + assert conf.config is not None, repr(probability) + + +def test_malformed_preserves_config(conf): + probabilities = [('50%', '50:0:0'), ('-0', '0')] + for seed in range(20): + for probability, equivalent in probabilities: + context = 'probability={!r}, equivalent={!r}, seed={}'.format( + probability, equivalent, seed) + assert conf._run_conf('--randconfig', extra_env={ + 'KCONFIG_PROBABILITY': probability, + 'KCONFIG_SEED': hex(seed), + }) == 0, context + assert ('warning: KCONFIG_PROBABILITY has malformed format' in + conf.stderr), context + assert conf.stderr.count('warning:') == 1, context + malformed = conf.config + + assert conf._run_conf('--randconfig', extra_env={ + 'KCONFIG_PROBABILITY': equivalent, + 'KCONFIG_SEED': hex(seed), + }) == 0, context + assert 'warning:' not in conf.stderr, context + assert conf.config == malformed, context + + +def test_werror(conf): + probabilities = [ + ('', 0), ('50', 0), ('50%', 1), ('-0', 1), ('10:20:30:40', 1), + ] + for probability, status in probabilities: + for werror in ['', '0', '1']: + context = 'probability={!r}, werror={!r}'.format( + probability, werror) + assert conf._run_conf('--randconfig', extra_env={ + 'KCONFIG_PROBABILITY': probability, + 'KCONFIG_SEED': '0', + 'KCONFIG_WERROR': werror, + }) == status, context + if status: + assert ('warning: KCONFIG_PROBABILITY has malformed format' in + conf.stderr), context + assert conf.stderr.count('warning:') == 1, context + else: + assert 'warning:' not in conf.stderr, context + + +def test_out_of_range(conf): + probabilities = [ + '-1', '101', '+101', '0:101', '0:0:101', '60:41', '0:60:41', + '4294967296', '-4294967296', + '999999999999999999999999', '-999999999999999999999999', + ] + for probability in probabilities: + assert conf._run_conf('--randconfig', extra_env={ + 'KCONFIG_PROBABILITY': probability, + 'KCONFIG_SEED': '0', + }) == 1, repr(probability) + assert 'KCONFIG_PROBABILITY:' in conf.stderr, repr(probability) + + +def test_valid(conf): + probabilities = [ + ('0', 'n', 'n'), + ('0:0', 'n', 'n'), + ('100:0', 'y', 'y'), + ('0:100', 'y', 'm'), + ('100:0:0', 'y', 'n'), + ('0:100:0', 'n', 'y'), + ('0:0:100', 'n', 'm'), + ('000:000:100', 'n', 'm'), + ] + for probability, boolean, tristate in probabilities: + assert conf._run_conf('--randconfig', extra_env={ + 'KCONFIG_PROBABILITY': probability, + 'KCONFIG_SEED': '0', + }) == 0, repr(probability) + assert 'warning:' not in conf.stderr, repr(probability) + for symbol, value in [('BOOL', boolean), ('TRI', tristate)]: + if value == 'n': + expected = '# CONFIG_{} is not set'.format(symbol) + else: + expected = 'CONFIG_{}={}'.format(symbol, value) + assert expected in conf.config.splitlines(), repr(probability) + + +def test_single_probability_matches_tristate_split(conf): + for seed in range(20): + context = 'seed={}'.format(seed) + assert conf._run_conf('--randconfig', extra_env={ + 'KCONFIG_PROBABILITY': '50', + 'KCONFIG_SEED': hex(seed), + }) == 0, context + assert 'warning:' not in conf.stderr, context + single = conf.config + + # 50% boolean y; 25% tristate y, 25% m, and 50% n. + assert conf._run_conf('--randconfig', extra_env={ + 'KCONFIG_PROBABILITY': '50:25:25', + 'KCONFIG_SEED': hex(seed), + }) == 0, context + assert 'warning:' not in conf.stderr, context + assert conf.config == single, context + + +def test_empty(conf): + assert conf._run_conf('--randconfig', extra_env={ + 'KCONFIG_PROBABILITY': '', + 'KCONFIG_SEED': '0', + }) == 0 + assert 'warning:' not in conf.stderr From 651fb291c4c86b7be96720b129ef4619fbcef53c Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Fri, 18 Sep 2026 17:59:25 -0700 Subject: [PATCH 20/59] kconfig: Add "def_string", "def_int" and "def_hex" Kconfig has offered "def_bool" and "def_tristate" as a shorthand for a type definition plus a default since before the git era, but has never offered the equivalent for the other three types. Conor Dooley ran into this gap[1] when fixing a symbol that had been given the wrong type: Unfortunately, there is no such thing as "def_string", but in this case we can use "default" to propagate the value of ... Nothing in the grammar requires the restriction. The rule that consumes a default is already type agnostic. Add the three missing types. No changes are needed to existing diagnostics. E.g. declaring a symbol "bool" and then assigning it with "def_string" still reports warning: ignoring type redefinition of 'CONFLICT' from 'bool' to 'string' Added tests for the types. Build tested ARCH=x86_64 with GCC 16.2.0. Tests pass with "make testconfig". Link: https://lore.kernel.org/all/20230111104848.2088516-1-conor.dooley@microchip.com/ [1] Assisted-by: LLM Signed-off-by: Kees Cook Tested-by: Julian Braha Reviewed-by: Julian Braha Link: https://patch.msgid.link/20260919005929.4077729-1-kees@kernel.org Signed-off-by: Nathan Chancellor --- Documentation/kbuild/kconfig-language.rst | 13 ++++++++- scripts/kconfig/kconfig-sym-check.pl | 2 +- scripts/kconfig/lexer.l | 3 ++ scripts/kconfig/parser.y | 6 ++++ scripts/kconfig/tests/def_type/Kconfig | 28 +++++++++++++++++++ scripts/kconfig/tests/def_type/__init__.py | 18 ++++++++++++ .../kconfig/tests/def_type/expected_guard_n | 9 ++++++ .../kconfig/tests/def_type/expected_guard_y | 11 ++++++++ scripts/kconfig/tests/def_type/guard_n.config | 1 + scripts/kconfig/tests/def_type/guard_y.config | 1 + 10 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 scripts/kconfig/tests/def_type/Kconfig create mode 100644 scripts/kconfig/tests/def_type/__init__.py create mode 100644 scripts/kconfig/tests/def_type/expected_guard_n create mode 100644 scripts/kconfig/tests/def_type/expected_guard_y create mode 100644 scripts/kconfig/tests/def_type/guard_n.config create mode 100644 scripts/kconfig/tests/def_type/guard_y.config diff --git a/Documentation/kbuild/kconfig-language.rst b/Documentation/kbuild/kconfig-language.rst index d9338407c1c6..00402d43e0dc 100644 --- a/Documentation/kbuild/kconfig-language.rst +++ b/Documentation/kbuild/kconfig-language.rst @@ -113,11 +113,22 @@ applicable everywhere (see syntax). - type definition + default value:: - "def_bool"/"def_tristate" ["if" ] + "def_bool" ["if" ] + "def_tristate" ["if" ] + "def_string" ["if" ] + "def_int" ["if" ] + "def_hex" ["if" ] This is a shorthand notation for a type definition plus a value. Optionally dependencies for this default value can be added with "if". + The shorthand supplies the type once, and is otherwise an ordinary + default: it is the first entry of the list described above, so any + further "default" entries still apply when its "if" is not met. Since + that leaves the type definition inside one arm of a list, spelling the + type out on its own line reads better for a symbol with several + defaults. + - dependencies: "depends on" ["if" ] This defines a dependency for this menu entry. If multiple diff --git a/scripts/kconfig/kconfig-sym-check.pl b/scripts/kconfig/kconfig-sym-check.pl index daa5285fdefc..c8dd07f8b27c 100755 --- a/scripts/kconfig/kconfig-sym-check.pl +++ b/scripts/kconfig/kconfig-sym-check.pl @@ -90,7 +90,7 @@ foreach my $file (@files) { next; } - if (/^\s*(default|def_bool|def_tristate|select|depends\s+on|imply|visible\s+if|range|if|bool|tristate|int|hex|string|prompt)\s+(.+)\s*$/) { + if (/^\s*(default|def_bool|def_tristate|def_string|def_int|def_hex|select|depends\s+on|imply|visible\s+if|range|if|bool|tristate|int|hex|string|prompt)\s+(.+)\s*$/) { my $s = $2; $s =~ s/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'//g; $s =~ s/#.*//; diff --git a/scripts/kconfig/lexer.l b/scripts/kconfig/lexer.l index a6155422b4a6..1fa521199d4a 100644 --- a/scripts/kconfig/lexer.l +++ b/scripts/kconfig/lexer.l @@ -105,6 +105,9 @@ n [A-Za-z0-9_-] "comment" return T_COMMENT; "config" return T_CONFIG; "def_bool" return T_DEF_BOOL; +"def_hex" return T_DEF_HEX; +"def_int" return T_DEF_INT; +"def_string" return T_DEF_STRING; "def_tristate" return T_DEF_TRISTATE; "default" return T_DEFAULT; "depends" return T_DEPENDS; diff --git a/scripts/kconfig/parser.y b/scripts/kconfig/parser.y index 40ceb9908c6f..ecb18c213d83 100644 --- a/scripts/kconfig/parser.y +++ b/scripts/kconfig/parser.y @@ -53,6 +53,9 @@ struct menu *current_menu, *current_entry, *current_choice; %token T_CONFIG %token T_DEFAULT %token T_DEF_BOOL +%token T_DEF_HEX +%token T_DEF_INT +%token T_DEF_STRING %token T_DEF_TRISTATE %token T_DEPENDS %token T_ENDCHOICE @@ -309,6 +312,9 @@ type: default: T_DEFAULT { $$ = S_UNKNOWN; } | T_DEF_BOOL { $$ = S_BOOLEAN; } + | T_DEF_HEX { $$ = S_HEX; } + | T_DEF_INT { $$ = S_INT; } + | T_DEF_STRING { $$ = S_STRING; } | T_DEF_TRISTATE { $$ = S_TRISTATE; } /* if entry */ diff --git a/scripts/kconfig/tests/def_type/Kconfig b/scripts/kconfig/tests/def_type/Kconfig new file mode 100644 index 000000000000..fbee37a63179 --- /dev/null +++ b/scripts/kconfig/tests/def_type/Kconfig @@ -0,0 +1,28 @@ +# SPDX-License-Identifier: GPL-2.0 +# The def_ shorthands: a type definition plus a default value. + +config MODULES + bool "Enable loadable module support" + modules + default y + +config GUARD + bool "Guard symbol" + +config DEF_BOOL + def_bool GUARD + +config DEF_TRISTATE + def_tristate m if GUARD + +config DEF_STRING + def_string "guarded" if GUARD + default "fallback" + +config DEF_INT + def_int 64 if GUARD + default 32 + +config DEF_HEX + def_hex 0xdead if GUARD + default 0x0 diff --git a/scripts/kconfig/tests/def_type/__init__.py b/scripts/kconfig/tests/def_type/__init__.py new file mode 100644 index 000000000000..1ebaf5da07c8 --- /dev/null +++ b/scripts/kconfig/tests/def_type/__init__.py @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0 +""" +Set a symbol's type and its default value in one line. + +"def_bool", "def_tristate", "def_string", "def_int" and "def_hex" are +shorthand for a type definition plus a "default" property. Check that +each one sets the type, and that an "if" on the shorthand does not +disturb the usual default cascade: the shorthand is only the first arm +of the list, so a later "default" still applies when its condition is +not met. +""" + +def test(conf): + assert conf.olddefconfig(dot_config='guard_y.config') == 0 + assert conf.config_matches('expected_guard_y') + + assert conf.olddefconfig(dot_config='guard_n.config') == 0 + assert conf.config_matches('expected_guard_n') diff --git a/scripts/kconfig/tests/def_type/expected_guard_n b/scripts/kconfig/tests/def_type/expected_guard_n new file mode 100644 index 000000000000..14719720a8b9 --- /dev/null +++ b/scripts/kconfig/tests/def_type/expected_guard_n @@ -0,0 +1,9 @@ +# +# Automatically generated file; DO NOT EDIT. +# Main menu +# +CONFIG_MODULES=y +# CONFIG_GUARD is not set +CONFIG_DEF_STRING="fallback" +CONFIG_DEF_INT=32 +CONFIG_DEF_HEX=0x0 diff --git a/scripts/kconfig/tests/def_type/expected_guard_y b/scripts/kconfig/tests/def_type/expected_guard_y new file mode 100644 index 000000000000..b2844072d0b8 --- /dev/null +++ b/scripts/kconfig/tests/def_type/expected_guard_y @@ -0,0 +1,11 @@ +# +# Automatically generated file; DO NOT EDIT. +# Main menu +# +CONFIG_MODULES=y +CONFIG_GUARD=y +CONFIG_DEF_BOOL=y +CONFIG_DEF_TRISTATE=m +CONFIG_DEF_STRING="guarded" +CONFIG_DEF_INT=64 +CONFIG_DEF_HEX=0xdead diff --git a/scripts/kconfig/tests/def_type/guard_n.config b/scripts/kconfig/tests/def_type/guard_n.config new file mode 100644 index 000000000000..ed9ad6c1a2d4 --- /dev/null +++ b/scripts/kconfig/tests/def_type/guard_n.config @@ -0,0 +1 @@ +# CONFIG_GUARD is not set diff --git a/scripts/kconfig/tests/def_type/guard_y.config b/scripts/kconfig/tests/def_type/guard_y.config new file mode 100644 index 000000000000..afe35b084542 --- /dev/null +++ b/scripts/kconfig/tests/def_type/guard_y.config @@ -0,0 +1 @@ +CONFIG_GUARD=y From 889a621272f9b7e19f35efa3d836347b4e209290 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Fri, 18 Sep 2026 17:59:26 -0700 Subject: [PATCH 21/59] kconfig: Replace "cc-option-bit" with "cc-option-str" $(cc-option-bit) returns the flag it was given when the compiler accepts it, and an empty string otherwise. It was originally only used internally for machine bit flags, but it is generally useful[1]. Rename it to $(cc-option-str), which says what it returns rather than what it was first used for, and extract the probe logic from the existing $(cc-option) macro to reuse it. Additionally add support for an optional fallback, much like the Kbuild cc-option macro. This can be used as: config CC_OPT_FOO def_string "$(cc-option-str,-fnew-option,-fold-option)" If the fallback is omitted, it is just an empty string. Build tested ARCH=x86_64 defconfig with GCC 16.2.0. Assisted-by: LLM Signed-off-by: Kees Cook Tested-by: Julian Braha Reviewed-by: Julian Braha Link: https://patch.msgid.link/20260919005929.4077729-2-kees@kernel.org Signed-off-by: Nathan Chancellor --- scripts/Kconfig.include | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/scripts/Kconfig.include b/scripts/Kconfig.include index fc10671c297c..4ef2b64e97be 100644 --- a/scripts/Kconfig.include +++ b/scripts/Kconfig.include @@ -23,9 +23,19 @@ success = $(if-success,$(1),y,n) # Return n if exits with 0, y otherwise failure = $(if-success,$(1),n,y) +# $(cc-probe,) +# The command $(cc-option) and $(cc-option-str) test with. Not meant +# to be called directly; use one of those two instead. +cc-probe = trap "rm -rf .tmp_$$" EXIT; mkdir .tmp_$$; $(CC) -Werror $(CLANG_FLAGS) $(1) -c -x c /dev/null -o .tmp_$$/tmp.o + # $(cc-option,) # Return y if the compiler supports , n otherwise -cc-option = $(success,trap "rm -rf .tmp_$$" EXIT; mkdir .tmp_$$; $(CC) -Werror $(CLANG_FLAGS) $(1) -c -x c /dev/null -o .tmp_$$/tmp.o) +cc-option = $(success,$(cc-probe,$(1))) + +# $(cc-option-str,[,]) +# Return if the compiler supports it, otherwise. +# defaults to an empty string and is returned untested. +cc-option-str = $(if-success,$(cc-probe,$(1)),$(1),$(2)) # $(ld-option,) # Return y if the linker supports , n otherwise @@ -61,9 +71,8 @@ ld-version := $(shell,set -- $(ld-info) && echo $2) # machine bit flags # $(m32-flag): -m32 if the compiler supports it, or an empty string otherwise. # $(m64-flag): -m64 if the compiler supports it, or an empty string otherwise. -cc-option-bit = $(if-success,$(CC) -Werror $(1) -E -x c /dev/null -o /dev/null,$(1)) -m32-flag := $(cc-option-bit,-m32) -m64-flag := $(cc-option-bit,-m64) +m32-flag := $(cc-option-str,-m32) +m64-flag := $(cc-option-str,-m64) # Test whether the compiler can link userspace applications cc_can_link_user = $(success,$(srctree)/scripts/cc-can-link.sh $(CC) $(CLANG_FLAGS) $(USERCFLAGS) $(USERLDFLAGS) $(1)) From cfcc465018818fb4f08148a28adb5b0cb40a9ec1 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Fri, 18 Sep 2026 17:59:27 -0700 Subject: [PATCH 22/59] kconfig: Add "ld-option-str" Add the linker counterpart to $(cc-option-str), sharing a $(ld-probe) with $(ld-option) the way the compiler macros share $(cc-probe). Like $(cc-option-str), it can now be used as: config LD_OPT_FOO def_string "$(ld-option-str,--new-option,--old-option)" An omitted fallback expands to the empty string. Build tested ARCH=x86_64 defconfig with GCC 16.2.0, and checked against GNU ld 2.47. Assisted-by: LLM Signed-off-by: Kees Cook Tested-by: Julian Braha Reviewed-by: Julian Braha Link: https://patch.msgid.link/20260919005929.4077729-3-kees@kernel.org Signed-off-by: Nathan Chancellor --- scripts/Kconfig.include | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/scripts/Kconfig.include b/scripts/Kconfig.include index 4ef2b64e97be..622d21e6953f 100644 --- a/scripts/Kconfig.include +++ b/scripts/Kconfig.include @@ -37,9 +37,19 @@ cc-option = $(success,$(cc-probe,$(1))) # defaults to an empty string and is returned untested. cc-option-str = $(if-success,$(cc-probe,$(1)),$(1),$(2)) +# $(ld-probe,) +# The command $(ld-option) and $(ld-option-str) test with. Not meant +# to be called directly; use one of those two instead. +ld-probe = $(LD) -v $(1) + # $(ld-option,) # Return y if the linker supports , n otherwise -ld-option = $(success,$(LD) -v $(1)) +ld-option = $(success,$(ld-probe,$(1))) + +# $(ld-option-str,[,]) +# Return if the linker supports it, otherwise. +# defaults to an empty string and is returned untested. +ld-option-str = $(if-success,$(ld-probe,$(1)),$(1),$(2)) # $(as-instr,) # Return y if the assembler supports , n otherwise From 5fe4dd59664105a9ea1a9468aa7391806b80364a Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Wed, 23 Sep 2026 18:17:50 +0100 Subject: [PATCH 23/59] kbuild: do not allocate .modinfo in vmlinux Commit 3e86e4d74c04 ("kbuild: keep .modinfo section in vmlinux.unstripped") placed MODULE_INFO() strings in the .modinfo section in vmlinux.unstripped, however it left the section allocatable, so it gets assigned an address and on arm64, arm and riscv, the section is tagged as PT_LOAD. This is useless as the data is ultimately stripped anyway. Doing this results in a lot of unnecessary work - each pass copies the whole file, 450 MiB with relocations for an x86 allmodconfig build and 250 MiB for an arm64 allmodconfig build. This adds ~0.7s on the serial tail of every build for arm64 and ~0.2s for x86 (the tail is single-threaded work done after parallel work has finished). Nothing requires .modinfo to exist at an address, so mark the output section (INFO) so that it's not allocated and use --dump-section to extract it as -O only emits allocated sections. The dump doesn't pick up the arch's OBJCOPYFLAGS any longer as they should not impact the output of this section. Image, bzImage, System.map and modules.builtin.modinfo are unchanged and the stripped vmlinux differs only in its program headers. The objcopy passes are on the serial tail of every build that links vmlinux, no-op builds are unchanged. Whole build, 128-thread Threadripper 9980X, best of N runs: before after delta ------------------------------- x86 defconfig, touch mm/vma.c, gcc 11.2s 11.1s -0.15s (-1%) x86 defconfig, touch mm/vma.c, clang 11.3s 11.1s -0.19s (-2%) x86 defconfig, clean, gcc 32.5s 32.3s -0.16s (-1%) x86 defconfig, clean, clang 34.5s 34.2s -0.26s (-1%) x86 allmodconfig, touch mm/vma.c, gcc 40.1s 39.7s -0.44s (-1%) Assisted-by: LLM Reviewed-by: Kees Cook Signed-off-by: Lorenzo Stoakes (ARM) Reviewed-by: Nicolas Schier Link: https://patch.msgid.link/20260923-build-speedup-v4-1-73128809a4a4@kernel.org Signed-off-by: Nathan Chancellor --- include/asm-generic/vmlinux.lds.h | 2 +- scripts/Makefile.vmlinux | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h index 26201821ea7f..a6730d34e8c6 100644 --- a/include/asm-generic/vmlinux.lds.h +++ b/include/asm-generic/vmlinux.lds.h @@ -855,7 +855,7 @@ KLP_SYMID #define MODINFO \ - .modinfo : { *(.modinfo) } + .modinfo (INFO) : { *(.modinfo) } #ifdef CONFIG_GENERIC_BUG #define BUG_TABLE \ diff --git a/scripts/Makefile.vmlinux b/scripts/Makefile.vmlinux index 6833b517cacb..95b523fe4f9e 100644 --- a/scripts/Makefile.vmlinux +++ b/scripts/Makefile.vmlinux @@ -89,11 +89,8 @@ remove-section-$(CONFIG_ARCH_VMLINUX_NEEDS_RELOCS) += '.rel.*' remove-symbols := -w --strip-unneeded-symbol='__mod_device_table__*' -# To avoid warnings: "empty loadable segment detected at ..." from GNU objcopy, -# it is necessary to remove the PT_LOAD flag from the segment. quiet_cmd_strip_relocs = OBJCOPY $@ - cmd_strip_relocs = $(OBJCOPY) $(patsubst %,--set-section-flags %=noload,$(remove-section-y)) $< $@; \ - $(OBJCOPY) $(addprefix --remove-section=,$(remove-section-y)) $(remove-symbols) $@ + cmd_strip_relocs = $(OBJCOPY) $(addprefix --remove-section=,$(remove-section-y)) $(remove-symbols) $< $@ targets += vmlinux vmlinux: vmlinux.unstripped FORCE @@ -103,10 +100,7 @@ vmlinux: vmlinux.unstripped FORCE # --------------------------------------------------------------------------- quiet_cmd_modules_builtin_modinfo = GEN $@ - cmd_modules_builtin_modinfo = $(cmd_objcopy); \ - chmod -x $@ - -OBJCOPYFLAGS_modules.builtin.modinfo := -j .modinfo -O binary + cmd_modules_builtin_modinfo = $(OBJCOPY) -O binary -j .modinfo --dump-section .modinfo=$@ $< /dev/null targets += modules.builtin.modinfo modules.builtin.modinfo: vmlinux.unstripped FORCE From 6f014bf46faf1645b6336f47390167de5c88e9d1 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Wed, 23 Sep 2026 18:17:51 +0100 Subject: [PATCH 24/59] kallsyms: index symbols by token to speed up table compression The kallsyms program compresses symbols by figuring out the most commonly used substrings in all of the input symbols then uses special character codes to represent them. For instance, 0xf7 might end up representing "write_", then every single symbol that contains "write_" can use 0xf7 as a shorthand and save 5 bytes each time. 'Special' character codes are any byte value that is not used in any symbol, either due to being an invalid character, or not being present in any symbol (e.g. if no symbol contains 'z', then 'z' can be used as special character). It does this by first figuring out which special characters are available in insert_real_symbols_in_table(), then iterating through every available special character, counting how many times each pair of adjacent characters appear in symbols in build_initial_token_table(). These adjacent pairs are known as 'tokens'. Token counts are initially obtained by build_initial_token_table(), then optimize_result() calls find_best_token() to determine the token that appeared the most number of times and assigns it the next special character. Finally, optimize_result() calls compress_symbols() to replace every token in every symbol with its special character, which updates token_profit[] as it does so. This process is repeated for each remaining available special character, with tokens now perhaps containing previously assigned special characters (e.g. if 'wr' was assigned 0x80, then the token representing 'wri' would be '\x80i'). This compresses that token by 50% in each symbol it appears in (two bytes are now represented by one) and thus by repeatedly doing this kallsyms obtains good symbol compression. However, compress_symbols() is seriously inefficient - it iterates through EVERY symbol for EVERY special character assignment, i.e. ~256 * nr_symbols. Modern x86-64 kernels, for instance, have ~158,000 symbols, so millions of iterations are performed, most of which are entirely unnecessary (tokens don't appear in most symbols). In practice kallsyms spends half its runtime doing this, two or three times per vmlinux link step. Fix this by tracking which symbols each token appears in token_syms[], and only compress symbols which actually need to be updated. Each time a token is compressed that token can no longer appear in any symbol, so that token_syms[] entry can be freed. However new token_syms[] entries must be created for each new token containing the assigned special character, but this is bounded by the number of replacements in the symbol which is very small. In testing on an x86-64 platform using clang, each kallsyms invocation dropped from 0.59s to 0.33s with CONFIG_KALLSYMS_ALL set and from 0.38s to 0.22s without it set. The data was carefully checked and verified to be byte-for-byte identical for six symbol sets (two vmlinux passes, vmlinux.o, three userspace binaries) with all option combinations. As part of this change, additionally refactor the code to be a little easier to follow. kallsyms runs two or three times on the serial tail of every build that links vmlinux, no-op builds do not link and are unchanged. Whole build, 128-thread Threadripper 9980X, best of N runs: before after delta ------------------------------- x86 defconfig, touch mm/vma.c, gcc 11.1s 10.6s -0.53s (-5%) x86 defconfig, touch mm/vma.c, clang 11.1s 10.5s -0.64s (-6%) x86 defconfig, clean, gcc 32.3s 31.3s -1.0s (-3%) x86 defconfig, clean, clang 34.2s 33.3s -0.89s (-3%) x86 allmodconfig, touch mm/vma.c, gcc 39.7s 38.7s -1.0s (-3%) x86 allmodconfig, touch mm/vma.c, clang 40.3s 39.1s -1.3s (-3%) Assisted-by: LLM Reviewed-by: Kees Cook Signed-off-by: Lorenzo Stoakes (ARM) Link: https://patch.msgid.link/20260923-build-speedup-v4-2-73128809a4a4@kernel.org Signed-off-by: Nathan Chancellor --- scripts/kallsyms.c | 143 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 129 insertions(+), 14 deletions(-) diff --git a/scripts/kallsyms.c b/scripts/kallsyms.c index 494852ade6d8..bb05de2aa580 100644 --- a/scripts/kallsyms.c +++ b/scripts/kallsyms.c @@ -58,12 +58,50 @@ static unsigned int table_size, table_cnt; static int all_symbols; static int pc_relative; -static int token_profit[0x10000]; +/* A dynamic array of symbols, encoded by symbol index. */ +struct sym_arr { + unsigned int *sym_indexes; + unsigned int cnt, cap; +}; + +/* Every pair of bytes is a token. */ +#define NR_TOKENS 0x10000 + +static int token_profit[NR_TOKENS]; +static struct sym_arr token_syms[NR_TOKENS]; /* the table that holds the result of the compression */ static unsigned char best_table[256][2]; static unsigned char best_table_len[256]; +static unsigned int sym_arr_last(const struct sym_arr *arr) +{ + return arr->cnt ? arr->sym_indexes[arr->cnt - 1] : UINT_MAX; +} + +static void sym_arr_maybe_expand(struct sym_arr *arr) +{ + if (arr->cap > arr->cnt) + return; + + arr->cap = arr->cap ? arr->cap * 2 : 16; + arr->sym_indexes = xrealloc(arr->sym_indexes, + arr->cap * sizeof(*arr->sym_indexes)); +} + +static void sym_arr_add(struct sym_arr *arr, unsigned int sym_idx) +{ + sym_arr_maybe_expand(arr); + arr->sym_indexes[arr->cnt++] = sym_idx; +} + +static void sym_arr_free(struct sym_arr *arr) +{ + free(arr->sym_indexes); + arr->sym_indexes = NULL; + arr->cnt = 0; + arr->cap = 0; +} static void usage(void) { @@ -458,6 +496,15 @@ static void write_src(void) printf("\n"); } +static unsigned int token_index(unsigned char first, unsigned char second) +{ + return first + (second << 8); +} + +static unsigned int sym_token_index(const unsigned char *symbol, int first_idx) +{ + return token_index(symbol[first_idx], symbol[first_idx + 1]); +} /* table lookup compression functions */ @@ -467,7 +514,7 @@ static void learn_symbol(const unsigned char *symbol, int len) int i; for (i = 0; i < len - 1; i++) - token_profit[ symbol[i] + (symbol[i + 1] << 8) ]++; + token_profit[sym_token_index(symbol, i)]++; } /* decrease the count for all the possible tokens in a symbol */ @@ -476,16 +523,76 @@ static void forget_symbol(const unsigned char *symbol, int len) int i; for (i = 0; i < len - 1; i++) - token_profit[ symbol[i] + (symbol[i + 1] << 8) ]--; + token_profit[sym_token_index(symbol, i)]--; +} + +static void token_add_symbol(unsigned int token_idx, unsigned int sym_idx) +{ + struct sym_arr *arr = &token_syms[token_idx]; + + /* Symbol indexes kept in sorted order, check for duplicate. */ + if (sym_arr_last(arr) == sym_idx) + return; + + sym_arr_add(arr, sym_idx); +} + +static void symbol_index_all_tokens(const unsigned char *symbol, int len, + unsigned int sym_idx) +{ + int i; + + for (i = 0; i < len - 1; i++) { + const unsigned int token_idx = sym_token_index(symbol, i); + + token_add_symbol(token_idx, sym_idx); + } +} + +/* + * The symbol just got compressed. The only parts of the symbol that changed + * meaningfully are those containing the newly assigned compressed char, so + * index those. + */ +static void symbol_index_new_tokens(const unsigned char *symbol, int len, + unsigned int sym_idx, int compressed_chr) +{ + int i; + + for (i = 0; i < len - 1; i++) { + const unsigned int token_idx = sym_token_index(symbol, i); + + if (symbol[i] == compressed_chr || + symbol[i + 1] == compressed_chr) + token_add_symbol(token_idx, sym_idx); + } } -/* do the initial token count */ static void build_initial_token_table(void) { unsigned int i; for (i = 0; i < table_cnt; i++) learn_symbol(table[i]->sym, table[i]->len); + + /* + * The initial occurrence counts tell us exactly how much memory should + * be reserved for each token's symbol array. + */ + for (i = 0; i < ARRAY_SIZE(token_syms); i++) { + const int nr_syms = token_profit[i]; + + if (!nr_syms) + continue; + + token_syms[i].cap = nr_syms; + token_syms[i].sym_indexes = + xmalloc(nr_syms * sizeof(unsigned int)); + } + + /* For every symbol, index every token -> symbol it is present in. */ + for (i = 0; i < table_cnt; i++) + symbol_index_all_tokens(table[i]->sym, table[i]->len, i); } static unsigned char *find_token(unsigned char *str, int len, @@ -502,27 +609,31 @@ static unsigned char *find_token(unsigned char *str, int len, /* replace a given token in all the valid symbols. Use the sampled symbols * to update the counts */ -static void compress_symbols(const unsigned char *str, int idx) +static void compress_symbols(const unsigned char *str, int compressed_chr) { - unsigned int i, len, size; + const unsigned int token_idx = sym_token_index(str, 0); + struct sym_arr *arr = &token_syms[token_idx]; + unsigned int sym_idx, j, len, size; unsigned char *p1, *p2; - for (i = 0; i < table_cnt; i++) { + /* Iterate through all symbols this token is found in and compress. */ + for (j = 0; j < arr->cnt; j++) { + sym_idx = arr->sym_indexes[j]; - len = table[i]->len; - p1 = table[i]->sym; + len = table[sym_idx]->len; + p1 = table[sym_idx]->sym; /* find the token on the symbol */ p2 = find_token(p1, len, str); if (!p2) continue; /* decrease the counts for this symbol's tokens */ - forget_symbol(table[i]->sym, len); + forget_symbol(table[sym_idx]->sym, len); size = len; do { - *p2 = idx; + *p2 = compressed_chr; p2++; size -= (p2 - p1); memmove(p2, p2 + 1, size); @@ -536,11 +647,15 @@ static void compress_symbols(const unsigned char *str, int idx) } while (p2); - table[i]->len = len; + table[sym_idx]->len = len; /* increase the counts for this symbol's new tokens */ - learn_symbol(table[i]->sym, len); + learn_symbol(table[sym_idx]->sym, len); + symbol_index_new_tokens(table[sym_idx]->sym, len, sym_idx, + compressed_chr); } + + sym_arr_free(arr); /* No symbol contains this token any more. */ } /* search the token with the maximum profit */ @@ -551,7 +666,7 @@ static int find_best_token(void) bestprofit=-10000; best = 0; - for (i = 0; i < 0x10000; i++) { + for (i = 0; i < ARRAY_SIZE(token_profit); i++) { if (token_profit[i] > bestprofit) { best = i; bestprofit = token_profit[i]; From 83da9f3197e50d2e2c5b8b8d8358b6be6e867d7f Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Wed, 23 Sep 2026 18:17:52 +0100 Subject: [PATCH 25/59] kallsyms: output binary data to speed output and kallsyms assembly kallsyms generates an assembly file that consists mostly of .byte entries containing compressed names, token strings and name-sorted sequence numbers. For an x86-64 build with 158k symbols that is a 37 MiB .S file which takes 0.57s to assemble each of the two to three times it is built over a kernel build. Each time it is generated it also takes kallsyms a similar amount of time to output it. Avoid this overhead by instead outputting this data as binary and importing it into the assembly using the .incbin directive. Tables that are wider than a byte remain part of the assembly to ensure endianness and relative relocations are performed correctly. With this change, the output assembly file shrinks from 37 MiB to 9.8 MiB, with a 2.6 MiB binary data file alongside it, and the object remains identical. The generated binary file is deleted correctly on build clean along with all other ephemeral data. On an x86-64 system with CONFIG_KALLSYMS_ALL set: before after delta scripts/kallsyms 0.24s 0.18s 0.06s assemble 0.57s 0.16s 0.41s Per kallsyms invocation/assembly, for a total of 0.47s time saving upon invocation. An incremental build on the same system was reduced from 11.15s to 9.65s, indicating a total of 1.5 seconds saved over the build. The kallsyms runs and their assembly are on the serial tail of every build that links vmlinux, no-op builds are unchanged. Whole build, 128-thread Threadripper 9980X, best of N runs: before after delta ------------------------------- x86 defconfig, touch mm/vma.c, gcc 10.6s 9.7s -0.89s (-8%) x86 defconfig, touch mm/vma.c, clang 10.5s 9.3s -1.2s (-11%) x86 defconfig, clean, gcc 31.3s 30.3s -1.0s (-3%) x86 defconfig, clean, clang 33.3s 31.7s -1.6s (-5%) x86 allmodconfig, touch mm/vma.c, gcc 38.7s 37.2s -1.4s (-4%) x86 allmodconfig, touch mm/vma.c, clang 39.1s 36.4s -2.6s (-7%) Assisted-by: LLM Reviewed-by: Kees Cook Signed-off-by: Lorenzo Stoakes (ARM) Link: https://patch.msgid.link/20260923-build-speedup-v4-3-73128809a4a4@kernel.org Signed-off-by: Nathan Chancellor --- scripts/kallsyms.c | 84 ++++++++++++++++++++++++++++++----------- scripts/link-vmlinux.sh | 2 +- 2 files changed, 64 insertions(+), 22 deletions(-) diff --git a/scripts/kallsyms.c b/scripts/kallsyms.c index bb05de2aa580..b615db767b1d 100644 --- a/scripts/kallsyms.c +++ b/scripts/kallsyms.c @@ -5,7 +5,10 @@ * This software may be used and distributed according to the terms * of the GNU General Public License, incorporated herein by reference. * - * Usage: kallsyms [--all-symbols] in.map > out.S + * Usage: kallsyms [--all-symbols] [--pc-relative] in.map out.bin > out.S + * + * The byte tables go to out.bin and are pulled into out.S with .incbin; + * wider tables stay assembler source for endianness and relocations. * * Table compression uses all the unused char codes on the symbols and * maps these to the most used substrings (tokens). For instance, it might @@ -105,7 +108,7 @@ static void sym_arr_free(struct sym_arr *arr) static void usage(void) { - fprintf(stderr, "Usage: kallsyms [--all-symbols] in.map > out.S\n"); + fprintf(stderr, "Usage: kallsyms [--all-symbols] [--pc-relative] in.map out.bin > out.S\n"); exit(1); } @@ -322,6 +325,26 @@ static void output_label(const char *label) printf("%s:\n", label); } +static long bin_pos(FILE *file) +{ + const long pos = ftell(file); + + if (pos < 0) { + perror("kallsyms: ftell"); + exit(EXIT_FAILURE); + } + + return pos; +} + +static void write_incbin(const char *filename, long start, long end) +{ + if (start >= end) + return; + + printf("\t.incbin \"%s\", %ld, %ld\n", filename, start, end - start); +} + /* uncompress a compressed symbol. When this function is called, the best table * might still be compressed itself, so the function needs to be recursive */ static int expand_symbol(const unsigned char *data, int len, char *result) @@ -374,11 +397,12 @@ static void sort_symbols_by_name(void) qsort(table, table_cnt, sizeof(table[0]), compare_names); } -static void write_src(void) +static void write_src(FILE *out_bin_file, const char *out_bin_name) { - unsigned int i, k, off; + unsigned int i, off; unsigned int best_idx[256]; unsigned int *markers, markers_cnt; + long bin_start; char buf[KSYM_NAME_LEN]; printf("\t.section .rodata, \"a\"\n"); @@ -393,6 +417,7 @@ static void write_src(void) markers = xmalloc(sizeof(*markers) * markers_cnt); output_label("kallsyms_names"); + bin_start = bin_pos(out_bin_file); off = 0; for (i = 0; i < table_cnt; i++) { if ((i & 0xFF) == 0) @@ -416,26 +441,24 @@ static void write_src(void) /* Encode length with ULEB128. */ if (table[i]->len <= 0x7F) { /* Most symbols use a single byte for the length. */ - printf("\t.byte 0x%02x", table[i]->len); + fputc(table[i]->len, out_bin_file); off += table[i]->len + 1; } else { /* "Big" symbols use two bytes. */ - printf("\t.byte 0x%02x, 0x%02x", - (table[i]->len & 0x7F) | 0x80, - (table[i]->len >> 7) & 0x7F); + fputc((table[i]->len & 0x7F) | 0x80, out_bin_file); + fputc((table[i]->len >> 7) & 0x7F, out_bin_file); off += table[i]->len + 2; } - for (k = 0; k < table[i]->len; k++) - printf(", 0x%02x", table[i]->sym[k]); + fwrite(table[i]->sym, 1, table[i]->len, out_bin_file); /* * Now that we wrote out the compressed symbol name, restore the - * original name and print it in the comment. + * original name for the comments below. */ expand_symbol(table[i]->sym, table[i]->len, buf); strcpy((char *)table[i]->sym, buf); - printf("\t/* %s */\n", table[i]->sym); } + write_incbin(out_bin_name, bin_start, bin_pos(out_bin_file)); printf(".size kallsyms_names, . - kallsyms_names\n"); printf("\n"); @@ -448,13 +471,15 @@ static void write_src(void) free(markers); output_label("kallsyms_token_table"); + bin_start = bin_pos(out_bin_file); off = 0; for (i = 0; i < 256; i++) { best_idx[i] = off; expand_symbol(best_table[i], best_table_len[i], buf); - printf("\t.asciz\t\"%s\"\n", buf); + fwrite(buf, 1, strlen(buf) + 1, out_bin_file); off += strlen(buf) + 1; } + write_incbin(out_bin_name, bin_start, bin_pos(out_bin_file)); printf(".size kallsyms_token_table, . - kallsyms_token_table\n"); printf("\n"); @@ -487,12 +512,13 @@ static void write_src(void) sort_symbols_by_name(); output_label("kallsyms_seqs_of_names"); - for (i = 0; i < table_cnt; i++) - printf("\t.byte 0x%02x, 0x%02x, 0x%02x\t/* %s */\n", - (unsigned char)(table[i]->seq >> 16), - (unsigned char)(table[i]->seq >> 8), - (unsigned char)(table[i]->seq >> 0), - table[i]->sym); + bin_start = bin_pos(out_bin_file); + for (i = 0; i < table_cnt; i++) { + fputc(table[i]->seq >> 16, out_bin_file); + fputc(table[i]->seq >> 8, out_bin_file); + fputc(table[i]->seq >> 0, out_bin_file); + } + write_incbin(out_bin_name, bin_start, bin_pos(out_bin_file)); printf("\n"); } @@ -803,6 +829,9 @@ static void sort_symbols(void) int main(int argc, char **argv) { + const char *out_bin_name; + FILE *out_bin_file; + while (1) { static const struct option long_options[] = { {"all-symbols", no_argument, &all_symbols, 1}, @@ -818,14 +847,27 @@ int main(int argc, char **argv) usage(); } - if (optind >= argc) + if (optind + 2 != argc) usage(); + out_bin_name = argv[optind + 1]; + out_bin_file = fopen(out_bin_name, "w"); + if (!out_bin_file) { + perror(out_bin_name); + exit(EXIT_FAILURE); + } + read_map(argv[optind]); shrink_table(); sort_symbols(); optimize_token_table(); - write_src(); + write_src(out_bin_file, out_bin_name); + + /* Write errors are latched in the stream, so one check at the end covers them all. */ + if (ferror(out_bin_file) || fclose(out_bin_file)) { + perror(out_bin_name); + exit(EXIT_FAILURE); + } return 0; } diff --git a/scripts/link-vmlinux.sh b/scripts/link-vmlinux.sh index ab0b8125c8cb..e88604150d2c 100755 --- a/scripts/link-vmlinux.sh +++ b/scripts/link-vmlinux.sh @@ -132,7 +132,7 @@ kallsyms() fi info KSYMS "${2}.S" - scripts/kallsyms ${kallsymopt} "${1}" > "${2}.S" + scripts/kallsyms ${kallsymopt} "${1}" "${2}.bin" > "${2}.S" info AS "${2}.o" ${CC} ${NOSTDINC_FLAGS} ${LINUXINCLUDE} ${KBUILD_CPPFLAGS} \ From 4719513c0fa2a39a2410448120a83cb1a416b194 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Wed, 23 Sep 2026 18:17:53 +0100 Subject: [PATCH 26/59] sorttable: parse nm output correctly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sorttable reads a list of functions from vmlinux, provided by the output of nm, with the -S flag specified providing function sizes. It does so using fscanf(fp, "%16s %16s %c %*s\n", ...) reading address, size, type (a single character) and name. However, nm outputs no size for a symbol which has none, meaning that it misinterprets these entries - interpreting the type character as the size, and, if the name is a single character, ignores the newline and swallows the address of the next entry. Size-less single character names exist (e.g. the assemblers' loop counters left over as local absolute symbols): 0000000000000001 a i 000000000000000f a i 0000000000000050 a j 0000000000000052 a t 000000000000009f a i 0000000000000200 a i sorttable is looking for functions and these are not that, and with nm's output sorted, it just so happens to be that what's swallowed is never a function. However the next commit in this series stops sorting nm's output, so this bug can results in function names being lost. Fix the issue by using '%*[^\n]' in fscanf() rather than '%*s' such that the scan cannot move past the newline. Then, compare the address and size fields (which are always padded to the same width if output by nm) - if they differ, then this is a misread, so simply discard the entry. It's fine to discard these, as sorttable's raison d'être is to discover whether a call site is contained within a function and an empty function can't contain anything. Assisted-by: LLM Signed-off-by: Lorenzo Stoakes (ARM) Link: https://patch.msgid.link/20260923-build-speedup-v4-4-73128809a4a4@kernel.org Signed-off-by: Nathan Chancellor --- scripts/sorttable.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/scripts/sorttable.c b/scripts/sorttable.c index d7b50581c732..88e49a5c4251 100644 --- a/scripts/sorttable.c +++ b/scripts/sorttable.c @@ -322,10 +322,23 @@ static int parse_symbols(const char *fname) return -1; } - while (fscanf(fp, "%16s %16s %c %*s\n", addr_str, size_str, &type) == 3) { + while (fscanf(fp, "%16s %16s %c%*[^\n]", addr_str, size_str, &type) == 3) { uint64_t addr; uint64_t size; + /* + * nm outputs size-less entries with a missing 2nd value, which + * means fscanf() just misread its fields. + * + * These don't matter as a call site cannot be in an empty + * function, so just skip them. + * + * nm pads address and size to the same width, so if their + * widths differ, this is a misread. + */ + if (strlen(size_str) != strlen(addr_str)) + continue; + /* Only care about functions */ if (type != 't' && type != 'T' && type != 'W') continue; From c2970d8b52d132bf5152fba592205a2204d7be5a Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Wed, 23 Sep 2026 18:17:54 +0100 Subject: [PATCH 27/59] kbuild: do not sort nm output where the order is irrelevant Unless instructed otherwise, nm sorts by name. There are places where this is unnecessary - an invocation from scripts/sorttable every vmlinux link, scripts/check-function-names.sh run after every vmlinux.o link, and the x86 VOFFSET and ZOFFSET listings between vmlinux and bzImage. Both GNU nm and llvm-nm accept the same '-p' parameter to disable sorting in these instances, so use that to prevent this unnecessary work. Each nm run on its own, x86-64, median of 5: GNU nm 2.47 llvm-nm 22 before after delta before after delta defconfig sorttable, nm -S vmlinux 0.080s 0.039s -0.041s 0.265s 0.113s -0.152s check-function-names.sh 0.072s 0.033s -0.039s 0.244s 0.102s -0.142s VOFFSET 0.077s 0.034s -0.043s 0.261s 0.113s -0.148s ZOFFSET 0.007s 0.005s -0.002s 0.005s 0.005s 0.000s TOTAL -0.125s -0.442s allmodconfig sorttable, nm -S vmlinux 0.156s 0.063s -0.093s 0.539s 0.223s -0.316s check-function-names.sh 0.149s 0.054s -0.095s 0.537s 0.217s -0.320s VOFFSET 0.146s 0.052s -0.094s 0.524s 0.221s -0.303s ZOFFSET 0.007s 0.005s -0.002s 0.006s 0.004s -0.002s TOTAL -0.284s -0.941s llvm-nm appears to be a lot slower than GNU nm, so these builds naturally improve the most. The four run one after another in the serial tail of every build that links vmlinux so impact kernel builds directly. In an allmodconfig the decompressor is built in parallel while the modules, so only the two before the vmlinux link contribute to build time. The build outputs remain unchanged. Whole build, 128-thread Threadripper 9980X, best of N runs: before after delta --------------------------------- x86 defconfig, touch mm/vma.c, gcc 9.7s 9.5s -0.18s (-2%) x86 defconfig, touch mm/vma.c, clang 9.3s 9.0s -0.27s (-3%) x86 defconfig, clean, gcc 30.3s 30.1s -0.16s (-1%) x86 defconfig, clean, clang 31.7s 31.2s -0.55s (-2%) x86 allmodconfig, touch mm/vma.c, clang 36.4s 35.8s -0.67s (-2%) Assisted-by: LLM Reviewed-by: Nicolas Schier Reviewed-by: Kees Cook Signed-off-by: Lorenzo Stoakes (ARM) Link: https://patch.msgid.link/20260923-build-speedup-v4-5-73128809a4a4@kernel.org Signed-off-by: Nathan Chancellor --- arch/x86/boot/Makefile | 2 +- arch/x86/boot/compressed/Makefile | 2 +- scripts/check-function-names.sh | 3 ++- scripts/link-vmlinux.sh | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/arch/x86/boot/Makefile b/arch/x86/boot/Makefile index 3f9fb3698d66..4a9bce32586d 100644 --- a/arch/x86/boot/Makefile +++ b/arch/x86/boot/Makefile @@ -74,7 +74,7 @@ SETUP_OBJS = $(addprefix $(obj)/,$(setup-y)) sed-zoffset := -e 's/^\([0-9a-fA-F]*\) [a-zA-Z] \(startup_32\|efi.._stub_entry\|efi\(32\)\?_pe_entry\|input_data\|kernel_info\|_end\|_ehead\|_text\|_e\?data\|_e\?sbat\|z_.*\)$$/\#define ZO_\2 0x\1/p' quiet_cmd_zoffset = ZOFFSET $@ - cmd_zoffset = $(NM) $< | sed -n $(sed-zoffset) > $@ + cmd_zoffset = $(NM) -p $< | sed -n $(sed-zoffset) > $@ targets += zoffset.h $(obj)/zoffset.h: $(obj)/compressed/vmlinux FORCE diff --git a/arch/x86/boot/compressed/Makefile b/arch/x86/boot/compressed/Makefile index 06934f9691d6..6ec5e031db1d 100644 --- a/arch/x86/boot/compressed/Makefile +++ b/arch/x86/boot/compressed/Makefile @@ -76,7 +76,7 @@ HOST_EXTRACFLAGS += -I$(srctree)/tools/include sed-voffset := -e 's/^\([0-9a-fA-F]*\) [ABbCDGRSTtVW] \(_text\|__start_rodata\|_sinittext\|__inittext_end\|__bss_start\|_end\)$$/\#define VO_\2 _AC(0x\1,UL)/p' quiet_cmd_voffset = VOFFSET $@ - cmd_voffset = $(NM) $< | sed -n $(sed-voffset) > $@ + cmd_voffset = $(NM) -p $< | sed -n $(sed-voffset) > $@ targets += ../voffset.h diff --git a/scripts/check-function-names.sh b/scripts/check-function-names.sh index 08071133e5a5..94883e690627 100755 --- a/scripts/check-function-names.sh +++ b/scripts/check-function-names.sh @@ -13,7 +13,8 @@ if [ ! -f "$objfile" ]; then exit 1 fi -bad_symbols=$(${NM:-nm} "$objfile" | awk '$2 ~ /^[TtWw]$/ {print $3}' | grep -E '^(startup|exit|split|unlikely|hot|unknown)(\.|$)') +bad_symbols=$(${NM:-nm} -p "$objfile" | awk '$2 ~ /^[TtWw]$/ {print $3}' | + grep -E '^(startup|exit|split|unlikely|hot|unknown)(\.|$)') if [ -n "$bad_symbols" ]; then echo "$bad_symbols" | while read -r sym; do diff --git a/scripts/link-vmlinux.sh b/scripts/link-vmlinux.sh index e88604150d2c..970ca10f8fa9 100755 --- a/scripts/link-vmlinux.sh +++ b/scripts/link-vmlinux.sh @@ -161,7 +161,7 @@ mksysmap() sorttable() { - ${NM} -S ${1} > .tmp_vmlinux.nm-sort + ${NM} -p -S ${1} > .tmp_vmlinux.nm-sort ${objtree}/scripts/sorttable -s .tmp_vmlinux.nm-sort ${1} } From 8050568e0a46f944117970c3969f9316125bc34d Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Wed, 23 Sep 2026 18:17:55 +0100 Subject: [PATCH 28/59] kbuild: only emit vmlinux relocations when required A kernel build consists of more than one linking pass on vmlinux.o and vmlinux, at minimum two, and with CONFIG_KALLSYMS and BTF enabled on x86-64 for example there are 5 such stages. For architectures that build their own relocation tables (x86, riscv, mips, s390), vmlinux is linked with the --emit-relocs parameter specified. However, this is only required on the final vmlinux link. Symbol tables of trial links preceding it don't need it because they already check that System.map matches kallsyms symbols on each build. GNU ld is slow at emitting relocation tables, so this results in a reduction in build time. Whole build, 128-thread Threadripper 9980X, best of N runs: before after delta ------------------------------- x86 defconfig, touch mm/vma.c, gcc 9.5s 9.1s -0.42s (-4%) x86 defconfig, clean, gcc 30.1s 29.5s -0.59s (-2%) x86 allmodconfig, touch mm/vma.c, gcc 37.2s 35.8s -1.4s (-4%) Note that this has little impact on LLVM ld which performs this operation more efficiently. Assisted-by: LLM Reviewed-by: Nicolas Schier Reviewed-by: Kees Cook Signed-off-by: Lorenzo Stoakes (ARM) Link: https://patch.msgid.link/20260923-build-speedup-v4-6-73128809a4a4@kernel.org Signed-off-by: Nathan Chancellor --- Makefile | 3 ++- scripts/link-vmlinux.sh | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f6745d723f6d..ab3ba78376f7 100644 --- a/Makefile +++ b/Makefile @@ -1265,8 +1265,9 @@ ifdef CONFIG_LD_ORPHAN_WARN LDFLAGS_vmlinux += --orphan-handling=$(CONFIG_LD_ORPHAN_WARN_LEVEL) endif +# --emit-relocs is added by scripts/link-vmlinux.sh, for the final link only. ifneq ($(CONFIG_ARCH_VMLINUX_NEEDS_RELOCS),) -LDFLAGS_vmlinux += --emit-relocs --discard-none +LDFLAGS_vmlinux += --discard-none endif # Align the architecture of userspace programs with the kernel diff --git a/scripts/link-vmlinux.sh b/scripts/link-vmlinux.sh index 970ca10f8fa9..ed5ff97c0667 100755 --- a/scripts/link-vmlinux.sh +++ b/scripts/link-vmlinux.sh @@ -96,6 +96,11 @@ vmlinux_link() ldflags="${ldflags} ${wl}--strip-debug" fi + # Only the final link actually requires the relocations. + if [ "${output}" = "${VMLINUX}" ] && is_enabled CONFIG_ARCH_VMLINUX_NEEDS_RELOCS; then + ldflags="${ldflags} ${wl}--emit-relocs" + fi + if [ -n "${generate_map}" ]; then ldflags="${ldflags} ${wl}-Map=vmlinux.map" fi From 5005f8043c092777b853663845da8facb5eb1385 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Wed, 23 Sep 2026 18:17:56 +0100 Subject: [PATCH 29/59] elf-parse: add section flags, symbol binding and mapping helpers Extend elf-parse to be able to read the symbol table of vmlinux in kallsyms. This requires the ability to open ELF files in read-only mode, so provide elf_map_ro() to do so. It also requires accessors for section flags and symbol bindings, so provide these via shdr_flags() and sym_bind(). It's useful to be able to open an ELF file, map it, and then have easy access to its section headers, names and symbol table, so introduce struct elf_file to store this. In order to obtain this data also provide elf_open_ro() and elf_close(). Also, check for the file being an ELF file first in elf_parse(). This is the logical thing to check for first, but additionally prevents kallsyms from having to check this it self. Assisted-by: LLM Reviewed-by: Kees Cook Signed-off-by: Lorenzo Stoakes (ARM) Link: https://patch.msgid.link/20260923-build-speedup-v4-7-73128809a4a4@kernel.org Signed-off-by: Nathan Chancellor --- scripts/elf-parse.c | 103 ++++++++++++++++++++++++++++++++++++++------ scripts/elf-parse.h | 54 +++++++++++++++++++++++ 2 files changed, 144 insertions(+), 13 deletions(-) diff --git a/scripts/elf-parse.c b/scripts/elf-parse.c index 99869ff91a8c..ce1c2a670244 100644 --- a/scripts/elf-parse.c +++ b/scripts/elf-parse.c @@ -17,15 +17,21 @@ struct elf_funcs elf_parser; * Get the whole file as a programming convenience in order to avoid * malloc+lseek+read+free of many pieces. If successful, then mmap * avoids copying unused pieces; else just read the whole file. - * Open for both read and write. + * Open for both read and write if writable is true, otherwise open + * read-only. */ -static void *map_file(char const *fname, size_t *size) +static void *map_file(char const *fname, size_t *size, bool writable) { - int fd; + int fd, prot = PROT_READ, flags = MAP_PRIVATE; struct stat sb; void *addr = NULL; - fd = open(fname, O_RDWR); + if (writable) { + prot |= PROT_WRITE; + flags = MAP_SHARED; + } + + fd = open(fname, writable ? O_RDWR : O_RDONLY); if (fd < 0) { perror(fname); return NULL; @@ -39,7 +45,7 @@ static void *map_file(char const *fname, size_t *size) goto out; } - addr = mmap(0, sb.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); + addr = mmap(0, sb.st_size, prot, flags, fd, 0); if (addr == MAP_FAILED) { fprintf(stderr, "Could not mmap file: %s\n", fname); goto out; @@ -57,6 +63,12 @@ static int elf_parse(const char *fname, void *addr, uint32_t types) Elf_Ehdr *ehdr = addr; uint16_t type; + if (memcmp(ELFMAG, ehdr->e32.e_ident, SELFMAG) != 0 || + ehdr->e32.e_ident[EI_VERSION] != EV_CURRENT) { + fprintf(stderr, "unrecognized ELF file %s\n", fname); + return -1; + } + switch (ehdr->e32.e_ident[EI_DATA]) { case ELFDATA2LSB: elf_parser.r = rle; @@ -78,12 +90,6 @@ static int elf_parse(const char *fname, void *addr, uint32_t types) return -1; } - if (memcmp(ELFMAG, ehdr->e32.e_ident, SELFMAG) != 0 || - ehdr->e32.e_ident[EI_VERSION] != EV_CURRENT) { - fprintf(stderr, "unrecognized ELF file %s\n", fname); - return -1; - } - type = elf_parser.r2(&ehdr->e32.e_type); if (!((1 << type) & types)) { fprintf(stderr, "Invalid ELF type file %s\n", fname); @@ -103,7 +109,9 @@ static int elf_parse(const char *fname, void *addr, uint32_t types) elf_parser.shdr_name = shdr32_name; elf_parser.shdr_type = shdr32_type; elf_parser.shdr_entsize = shdr32_entsize; + elf_parser.shdr_flags = shdr32_flags; elf_parser.sym_type = sym32_type; + elf_parser.sym_bind = sym32_bind; elf_parser.sym_name = sym32_name; elf_parser.sym_value = sym32_value; elf_parser.sym_shndx = sym32_shndx; @@ -133,7 +141,9 @@ static int elf_parse(const char *fname, void *addr, uint32_t types) elf_parser.shdr_name = shdr64_name; elf_parser.shdr_type = shdr64_type; elf_parser.shdr_entsize = shdr64_entsize; + elf_parser.shdr_flags = shdr64_flags; elf_parser.sym_type = sym64_type; + elf_parser.sym_bind = sym64_bind; elf_parser.sym_name = sym64_name; elf_parser.sym_value = sym64_value; elf_parser.sym_shndx = sym64_shndx; @@ -174,12 +184,13 @@ int elf_map_long_size(void *addr) return ehdr->e32.e_ident[EI_CLASS] == ELFCLASS32 ? 4 : 8; } -void *elf_map(char const *fname, size_t *size, uint32_t types) +static void *__elf_map(char const *fname, size_t *size, uint32_t types, + bool writable) { void *addr; int ret; - addr = map_file(fname, size); + addr = map_file(fname, size, writable); if (!addr) return NULL; @@ -192,7 +203,73 @@ void *elf_map(char const *fname, size_t *size, uint32_t types) return addr; } +void *elf_map(char const *fname, size_t *size, uint32_t types) +{ + return __elf_map(fname, size, types, true); +} + +/* For tools that only read the file. */ +void *elf_map_ro(char const *fname, size_t *size, uint32_t types) +{ + return __elf_map(fname, size, types, false); +} + void elf_unmap(void *addr, size_t size) { munmap(addr, size); } + +/* + * Open an ELF file and map it read-only, locating its section headers, section + * names and symbol table which are populated in the elf out parameter. + * + * Returns 0 on success, otherwise -1. + */ +int elf_open_ro(char const *fname, uint32_t types, struct elf_file *elf) +{ + Elf_Ehdr *ehdr; + Elf_Shdr *first; + unsigned int shstrndx, i; + + memset(elf, 0, sizeof(*elf)); + elf->base = elf_map_ro(fname, &elf->size, types); + if (!elf->base) + return -1; + + ehdr = elf->base; + elf->shdrs = (const char *)elf->base + ehdr_shoff(ehdr); + elf->shentsize = ehdr_shentsize(ehdr); + first = elf_section(elf, 0); + + /* A count or index too large for the header lives in section 0. */ + elf->shnum = ehdr_shnum(ehdr); + if (elf->shnum == SHN_UNDEF) + elf->shnum = shdr_size(first); + shstrndx = ehdr_shstrndx(ehdr); + if (shstrndx == SHN_XINDEX) + shstrndx = shdr_link(first); + elf->shstrtab = (const char *)elf->base + + shdr_offset(elf_section(elf, shstrndx)); + + for (i = 0; i < elf->shnum && !elf->symtab; i++) + if (shdr_type(elf_section(elf, i)) == SHT_SYMTAB) + elf->symtab = elf_section(elf, i); + + if (!elf->symtab) { + fprintf(stderr, "%s: no symbol table\n", fname); + elf_close(elf); + return -1; + } + + elf->strtab = (const char *)elf->base + + shdr_offset(elf_section(elf, shdr_link(elf->symtab))); + elf->nr_syms = shdr_size(elf->symtab) / shdr_entsize(elf->symtab); + + return 0; +} + +void elf_close(struct elf_file *elf) +{ + elf_unmap(elf->base, elf->size); + elf->base = NULL; +} diff --git a/scripts/elf-parse.h b/scripts/elf-parse.h index f4411e03069d..c5ad754da254 100644 --- a/scripts/elf-parse.h +++ b/scripts/elf-parse.h @@ -37,10 +37,12 @@ struct elf_funcs { uint64_t (*shdr_offset)(Elf_Shdr *shdr); uint64_t (*shdr_size)(Elf_Shdr *shdr); uint64_t (*shdr_entsize)(Elf_Shdr *shdr); + uint64_t (*shdr_flags)(Elf_Shdr *shdr); uint32_t (*shdr_link)(Elf_Shdr *shdr); uint32_t (*shdr_name)(Elf_Shdr *shdr); uint32_t (*shdr_type)(Elf_Shdr *shdr); uint8_t (*sym_type)(Elf_Sym *sym); + uint8_t (*sym_bind)(Elf_Sym *sym); uint32_t (*sym_name)(Elf_Sym *sym); uint64_t (*sym_value)(Elf_Sym *sym); uint16_t (*sym_shndx)(Elf_Sym *sym); @@ -143,6 +145,7 @@ SHDR_ADDR(addr) SHDR_ADDR(offset) SHDR_ADDR(size) SHDR_ADDR(entsize) +SHDR_ADDR(flags) SHDR_WORD(link) SHDR_WORD(name) @@ -211,6 +214,21 @@ static inline uint8_t sym_type(Elf_Sym *sym) return elf_parser.sym_type(sym); } +static inline uint8_t sym64_bind(Elf_Sym *sym) +{ + return ELF64_ST_BIND(sym->e64.st_info); +} + +static inline uint8_t sym32_bind(Elf_Sym *sym) +{ + return ELF32_ST_BIND(sym->e32.st_info); +} + +static inline uint8_t sym_bind(Elf_Sym *sym) +{ + return elf_parser.sym_bind(sym); +} + SYM_ADDR(value) SYM_WORD(name) SYM_HALF(shndx) @@ -298,8 +316,44 @@ static inline void w8le(uint64_t val, uint64_t *x) } void *elf_map(char const *fname, size_t *size, uint32_t types); +void *elf_map_ro(char const *fname, size_t *size, uint32_t types); void elf_unmap(void *addr, size_t size); int elf_map_machine(void *addr); int elf_map_long_size(void *addr); +/* A mapped file with its section headers, section names and symbol table. */ +struct elf_file { + void *base; + size_t size; + const char *shdrs; + unsigned int shnum, shentsize; + const char *shstrtab; + Elf_Shdr *symtab; + const char *strtab; + size_t nr_syms; +}; + +int elf_open_ro(char const *fname, uint32_t types, struct elf_file *elf); +void elf_close(struct elf_file *elf); + +static inline Elf_Shdr *elf_section(const struct elf_file *elf, + unsigned int index) +{ + return (Elf_Shdr *)(elf->shdrs + (size_t)index * elf->shentsize); +} + +static inline const char *elf_section_name(const struct elf_file *elf, + Elf_Shdr *shdr) +{ + return elf->shstrtab + shdr_name(shdr); +} + +static inline Elf_Sym *elf_symbol(const struct elf_file *elf, size_t index) +{ + const char *base = elf->base; + + return (Elf_Sym *)(base + shdr_offset(elf->symtab) + + index * shdr_entsize(elf->symtab)); +} + #endif /* _SCRIPTS_ELF_PARSE_H */ From 531980b4b17e916f5955bcc53a188827624f5a0f Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Wed, 23 Sep 2026 18:17:57 +0100 Subject: [PATCH 30/59] kallsyms: reimplement mksysmap in C mksysmap is a sed script consisting of 30 patterns which link-vmlinux.sh uses to generate *.syms files, and which kallsyms is then called against to generate *.kallsyms files, with the final vmlinux build ultimately generating System.map. For an x86-64 allmodconfig build, this involves three nm runs over a 250 MiB file and half a million lines written and read each time - 0.5s per pass for llvm-nm, and 0.2s for GNU nm, with parsing on top of that. This is unnecessary, instead have kallsyms simply read the ELF file directly making use of the existing elf-parse library in scripts/. This changes kallsyms such that its input is no longer the output from nm, but rather an ELF file. However, if the input file is empty, it outputs an empty table, which retains the same behaviour on first pass that the build system expects. System.map is byte-identical to nm | mksysmap for GNU nm and llvm-nm on two x86 configurations each, and for llvm-nm on arm64, arm, s390 and loongarch defconfigs, so are the kallsyms tables of every pass. Relinking vmlinux, link steps included: before after allmodconfig clang 9.1s 7.9s allmodconfig gcc 7.8s 7.6s defconfig clang 3.7s 3.0s defconfig gcc 3.5s 3.4s Whole build, 128-thread Threadripper 9980X, best of N runs: before after delta ------------------------------- x86 defconfig, touch mm/vma.c, gcc 9.1s 8.6s -0.43s (-5%) x86 defconfig, touch mm/vma.c, clang 9.0s 8.0s -1.1s (-12%) x86 defconfig, clean, gcc 29.5s 28.9s -0.64s (-2%) x86 defconfig, clean, clang 31.2s 29.8s -1.4s (-5%) x86 allmodconfig, touch mm/vma.c, gcc 35.8s 35.0s -0.77s (-2%) x86 allmodconfig, touch mm/vma.c, clang 35.8s 33.7s -2.0s (-6%) Assisted-by: LLM Signed-off-by: Lorenzo Stoakes (ARM) Link: https://patch.msgid.link/20260923-build-speedup-v4-8-73128809a4a4@kernel.org Signed-off-by: Nathan Chancellor --- scripts/Makefile | 5 +- scripts/kallsyms-sysmap.c | 248 ++++++++++++++++++++++++++++++++++++++ scripts/kallsyms.c | 164 ++++++++++++++----------- scripts/kallsyms.h | 44 +++++++ scripts/link-vmlinux.sh | 15 +-- scripts/mksysmap | 94 --------------- 6 files changed, 396 insertions(+), 174 deletions(-) create mode 100644 scripts/kallsyms-sysmap.c create mode 100644 scripts/kallsyms.h delete mode 100755 scripts/mksysmap diff --git a/scripts/Makefile b/scripts/Makefile index 3434a82a119f..38d58b0a37e6 100644 --- a/scripts/Makefile +++ b/scripts/Makefile @@ -3,7 +3,7 @@ # scripts contains sources for various helper programs used throughout # the kernel for the build process. -hostprogs-always-$(CONFIG_KALLSYMS) += kallsyms +hostprogs-always-y += kallsyms hostprogs-always-$(BUILD_C_RECORDMCOUNT) += recordmcount hostprogs-always-$(CONFIG_BUILDTIME_TABLE_SORT) += sorttable hostprogs-always-$(CONFIG_ASN1) += asn1_compiler @@ -13,6 +13,7 @@ hostprogs-always-$(CONFIG_RUST_KERNEL_DOCTESTS) += rustdoc_test_builder hostprogs-always-$(CONFIG_RUST_KERNEL_DOCTESTS) += rustdoc_test_gen hostprogs-always-$(CONFIG_TRACEPOINTS) += tracepoint-update +kallsyms-objs := kallsyms.o kallsyms-sysmap.o elf-parse.o sorttable-objs := sorttable.o elf-parse.o tracepoint-update-objs := tracepoint-update.o elf-parse.o @@ -30,6 +31,8 @@ rustdoc_test_builder-rust := y rustdoc_test_gen-rust := y HOSTCFLAGS_tracepoint-update.o = -I$(srctree)/tools/include +HOSTCFLAGS_kallsyms.o = -I$(srctree)/tools/include +HOSTCFLAGS_kallsyms-sysmap.o = -I$(srctree)/tools/include HOSTCFLAGS_elf-parse.o = -I$(srctree)/tools/include HOSTCFLAGS_sorttable.o = -I$(srctree)/tools/include HOSTLDLIBS_sorttable = -lpthread diff --git a/scripts/kallsyms-sysmap.c b/scripts/kallsyms-sysmap.c new file mode 100644 index 000000000000..a482968eba51 --- /dev/null +++ b/scripts/kallsyms-sysmap.c @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Obtain symbols from vmlinux for usage by kallsyms. Replaces mksysmap. + * + * To retain compatibility, it provides the same output as nm, only faster. + */ + +#include +#include +#include +#include +#include + +#include "elf-parse.h" +#include "kallsyms.h" + +/* + * What mksysmap dropped from System.map, by name: symbols that are not needed + * there, or not suitable for kallsyms. + */ +static const char *const sysmap_omit_prefixes[] = { + /* local symbols for ARM, MIPS, etc. */ + "$", + /* local labels, .LBB, .Ltmpxxx, .L__unnamed_xx, .LASANPC, etc. */ + ".L", + /* arm64 EFI stub namespace */ + "__efistub_", + /* arm64 local symbols in PIE namespace */ + "__pi_$", + "__pi_.L", + /* arm64 local symbols in non-VHE KVM namespace */ + "__kvm_nvhe_$", + "__kvm_nvhe_.L", + /* CFI type identifiers */ + "__kcfi_typeid_", + "__kvm_nvhe___kcfi_typeid_", + "__pi___kcfi_typeid_", + /* CRC from modversions */ + "__crc_", + /* EXPORT_SYMBOL (symbol name) */ + "__kstrtab_", + /* EXPORT_SYMBOL (namespace) */ + "__kstrtabns_", + /* MODULE_DEVICE_TABLE (symbol name) */ + "__mod_device_table__", +}; + +static const char *const sysmap_omit_suffixes[] = { + /* arm */ + "_from_arm", + "_from_thumb", + "_veneer", +}; + +static const char *const sysmap_omit_names[] = { + /* for LoongArch? */ + "L0", + /* ppc */ + "_SDA_BASE_", + "_SDA2_BASE_", +}; + +/* Symbols that contain the pattern anywhere are dropped. */ +static const char *const sysmap_omit_patterns[] = { + /* ppc stub */ + ".long_branch.", + ".plt_branch.", +}; + +/* __*Thunk_: the linker's range extension thunks on arm. */ +static bool is_range_thunk(const char *name) +{ + const char *p; + + if (!string_starts_with(name, "__")) + return false; + for (p = name + 2; isalnum((unsigned char)*p); p++) + ; + return p - name >= 7 && *p == '_' && strncmp(p - 5, "Thunk", 5) == 0; +} + +/* __UNIQUE_ID_modinfo_: the MODULE_INFO() strings of built-in code. */ +static bool is_modinfo_id(const char *name) +{ + static const char prefix[] = "__UNIQUE_ID_modinfo_"; + const char *p; + + if (!string_starts_with(name, prefix)) + return false; + for (p = name + strlen(prefix); isdigit((unsigned char)*p); p++) + ; + return !*p; +} + +static bool sysmap_omits(const char *name, char type) +{ + size_t i; + + /* Absolute, undefined and debugging symbols. */ + if (type == 'a' || type == 'N' || type == 'U' || type == 'w') + return true; + + for (i = 0; i < ARRAY_SIZE(sysmap_omit_prefixes); i++) + if (string_starts_with(name, sysmap_omit_prefixes[i])) + return true; + for (i = 0; i < ARRAY_SIZE(sysmap_omit_suffixes); i++) + if (string_ends_with(name, sysmap_omit_suffixes[i])) + return true; + for (i = 0; i < ARRAY_SIZE(sysmap_omit_names); i++) + if (strcmp(name, sysmap_omit_names[i]) == 0) + return true; + for (i = 0; i < ARRAY_SIZE(sysmap_omit_patterns); i++) + if (strstr(name, sysmap_omit_patterns[i])) + return true; + + return is_range_thunk(name) || is_modinfo_id(name); +} + +/* nm's letter for a symbol defined in a section, as BFD classifies it. */ +static char section_symbol_type(Elf_Shdr *shdr, const char *secname) +{ + static const char *const debug_prefixes[] = { + ".debug", ".zdebug", ".gnu.debuglto_.debug_", + ".gnu.linkonce.wi.", ".line", ".stab", + }; + const uint64_t flags = shdr_flags(shdr); + size_t i; + + if (flags & SHF_EXECINSTR) + return 't'; + if (flags & SHF_ALLOC) { + if (shdr_type(shdr) == SHT_NOBITS) + return 'b'; + return flags & SHF_WRITE ? 'd' : 'r'; + } + for (i = 0; i < ARRAY_SIZE(debug_prefixes); i++) + if (string_starts_with(secname, debug_prefixes[i])) + return 'N'; + if (shdr_type(shdr) != SHT_NOBITS && !(flags & SHF_WRITE)) + return 'n'; + return '?'; +} + +/* The letter nm prints for a symbol, or 0 for one it leaves out. */ +static char elf_symbol_type(const struct elf_file *elf, Elf_Sym *sym) +{ + unsigned int bind = sym_bind(sym), type = sym_type(sym); + unsigned int shndx = sym_shndx(sym); + Elf_Shdr *shdr; + char c; + + if (type == STT_SECTION || type == STT_FILE) + return 0; + if (shndx == SHN_COMMON) + return 'C'; + if (shndx == SHN_UNDEF) { + if (bind == STB_WEAK) + return type == STT_OBJECT ? 'v' : 'w'; + return 'U'; + } + if (type == STT_GNU_IFUNC) + return 'i'; + if (bind == STB_WEAK) + return type == STT_OBJECT ? 'V' : 'W'; + if (bind == STB_GNU_UNIQUE) + return 'u'; + if (bind != STB_GLOBAL && bind != STB_LOCAL) + return '?'; + + if (shndx == SHN_ABS) { + c = 'a'; + } else if (shndx < elf->shnum) { + shdr = elf_section(elf, shndx); + c = section_symbol_type(shdr, elf_section_name(elf, shdr)); + } else { + return '?'; + } + + return bind == STB_GLOBAL ? toupper(c) : c; +} + +/* nm -n order: by address, then by name. */ +static int compare_symbols(const void *a, const void *b) +{ + const struct sysmap_symbol *sa = a, *sb = b; + + if (sa->addr != sb->addr) + return sa->addr < sb->addr ? -1 : 1; + return strcmp(sa->name, sb->name); +} + +/* The symbols "nm -n | mksysmap" would list, in that order. */ +static struct sysmap_symbol *elf_read_symbols(const struct elf_file *elf, + size_t *nr_kept) +{ + struct sysmap_symbol *syms = xmalloc(elf->nr_syms * sizeof(*syms)); + size_t i, n = 0; + + for (i = 1; i < elf->nr_syms; i++) { + Elf_Sym *sym = elf_symbol(elf, i); + const char *name = elf->strtab + sym_name(sym); + char type = elf_symbol_type(elf, sym); + + if (!type || sysmap_omits(name, type)) + continue; + + syms[n].addr = sym_value(sym); + syms[n].name = name; + syms[n].type = type; + n++; + } + + qsort(syms, n, sizeof(*syms), compare_symbols); + *nr_kept = n; + return syms; +} + +struct sysmap *sysmap_read(const char *path) +{ + struct sysmap *map = xcalloc(1, sizeof(*map)); + + /* The names point into the mapping; keep it until the map is freed. */ + if (elf_open_ro(path, (1 << ET_EXEC) | (1 << ET_DYN), &map->elf)) + exit(EXIT_FAILURE); + map->syms = elf_read_symbols(&map->elf, &map->nr_syms); + map->addr_width = elf_map_long_size(map->elf.base) * 2; + + return map; +} + +void sysmap_write(const struct sysmap *map, FILE *out) +{ + size_t i; + + for (i = 0; i < map->nr_syms; i++) { + const struct sysmap_symbol *s = &map->syms[i]; + + fprintf(out, "%0*llx %c %s\n", map->addr_width, s->addr, s->type, + s->name); + } +} + +void sysmap_free(struct sysmap *map) +{ + free(map->syms); + elf_close(&map->elf); + free(map); +} diff --git a/scripts/kallsyms.c b/scripts/kallsyms.c index b615db767b1d..ecb444078c1c 100644 --- a/scripts/kallsyms.c +++ b/scripts/kallsyms.c @@ -5,7 +5,12 @@ * This software may be used and distributed according to the terms * of the GNU General Public License, incorporated herein by reference. * - * Usage: kallsyms [--all-symbols] [--pc-relative] in.map out.bin > out.S + * Usage: kallsyms [--all-symbols] [--pc-relative] [--sysmap=out.map] in out.bin > out.S + * kallsyms --sysmap=out.map in + * + * in is vmlinux; an empty file stands for the first link, which has no + * symbols yet, and gives an empty table. --sysmap also writes the symbols + * in System.map format. * * The byte tables go to out.bin and are pulled into out.S with .incbin; * wider tables stay assembler source for endianness and relocations. @@ -21,7 +26,6 @@ * */ -#include #include #include #include @@ -29,10 +33,10 @@ #include #include #include - +#include #include -#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0])) +#include "kallsyms.h" #define KSYM_NAME_LEN 512 @@ -108,11 +112,13 @@ static void sym_arr_free(struct sym_arr *arr) static void usage(void) { - fprintf(stderr, "Usage: kallsyms [--all-symbols] [--pc-relative] in.map out.bin > out.S\n"); + fprintf(stderr, "Usage: kallsyms [--all-symbols] [--pc-relative] [--sysmap=out.map]\n" + " in out.bin > out.S\n" + " kallsyms --sysmap=out.map vmlinux\n"); exit(1); } -static char *sym_name(const struct sym_entry *s) +static char *sym_entry_name(const struct sym_entry *s) { return (char *)s->sym + 1; } @@ -153,37 +159,12 @@ static void check_symbol_range(const char *sym, unsigned long long addr, } } -static struct sym_entry *read_symbol(FILE *in, char **buf, size_t *buf_len) +static struct sym_entry *add_symbol(unsigned long long addr, char type, + const char *name) { - char *name, type, *p; - unsigned long long addr; - size_t len; - ssize_t readlen; + size_t len = strlen(name); struct sym_entry *sym; - errno = 0; - readlen = getline(buf, buf_len, in); - if (readlen < 0) { - if (errno) { - perror("read_symbol"); - exit(EXIT_FAILURE); - } - return NULL; - } - - if ((*buf)[readlen - 1] == '\n') - (*buf)[readlen - 1] = 0; - - addr = strtoull(*buf, &p, 16); - - if (*buf == p || *p++ != ' ' || !isascii((type = *p++)) || *p++ != ' ') { - fprintf(stderr, "line format error\n"); - exit(EXIT_FAILURE); - } - - name = p; - len = strlen(name); - if (len >= KSYM_NAME_LEN) { fprintf(stderr, "Symbol %s too long for kallsyms (%zu >= %d).\n" "Please increase KSYM_NAME_LEN both in kernel and kallsyms.c\n", @@ -208,7 +189,7 @@ static struct sym_entry *read_symbol(FILE *in, char **buf, size_t *buf_len) sym->addr = addr; sym->len = len; sym->sym[0] = type; - strcpy(sym_name(sym), name); + strcpy(sym_entry_name(sym), name); return sym; } @@ -229,14 +210,9 @@ static int symbol_in_range(const struct sym_entry *s, return 0; } -static bool string_starts_with(const char *s, const char *prefix) -{ - return strncmp(s, prefix, strlen(prefix)) == 0; -} - static int symbol_valid(const struct sym_entry *s) { - const char *name = sym_name(s); + const char *name = sym_entry_name(s); /* if --all-symbols is not specified, then symbols outside the text * and inittext sections are discarded */ @@ -286,36 +262,55 @@ static void shrink_table(void) table_cnt = pos; } -static void read_map(const char *in) +static void add_table_entry(struct sym_entry *sym) { - FILE *fp; - struct sym_entry *sym; - char *buf = NULL; - size_t buflen = 0; + sym->seq = table_cnt; - fp = fopen(in, "r"); - if (!fp) { - perror(in); - exit(1); + if (table_cnt >= table_size) { + table_size = table_size ? table_size * 2 : 10000; + table = xrealloc(table, sizeof(*table) * table_size); } - while (!feof(fp)) { - sym = read_symbol(fp, &buf, &buflen); - if (!sym) - continue; + table[table_cnt++] = sym; +} - sym->seq = table_cnt; +static bool file_is_empty(const char *path) +{ + struct stat st; - if (table_cnt >= table_size) { - table_size += 10000; - table = xrealloc(table, sizeof(*table) * table_size); - } - - table[table_cnt++] = sym; + if (stat(path, &st)) { + perror(path); + exit(EXIT_FAILURE); } - free(buf); - fclose(fp); + return st.st_size == 0; +} + +/* + * Read the symbols from vmlinux, writing System.map if asked to. The first + * link has no symbols yet: an empty file gives an empty table. + */ +static void read_elf(const char *path, FILE *sysmap_out) +{ + struct sysmap *map; + size_t i; + + if (file_is_empty(path)) + return; + + map = sysmap_read(path); + if (sysmap_out) + sysmap_write(map, sysmap_out); + + for (i = 0; i < map->nr_syms; i++) { + const struct sysmap_symbol *s = &map->syms[i]; + struct sym_entry *sym = add_symbol(s->addr, s->type, s->name); + + if (sym) + add_table_entry(sym); + } + + sysmap_free(map); } static void output_label(const char *label) @@ -378,7 +373,7 @@ static int compare_names(const void *a, const void *b) const struct sym_entry *sa = *(const struct sym_entry **)a; const struct sym_entry *sb = *(const struct sym_entry **)b; - ret = strcmp(sym_name(sa), sym_name(sb)); + ret = strcmp(sym_entry_name(sa), sym_entry_name(sb)); if (!ret) { if (sa->addr > sb->addr) return 1; @@ -756,7 +751,7 @@ static void optimize_token_table(void) /* guess for "linker script provide" symbol */ static int may_be_linker_script_provide_symbol(const struct sym_entry *se) { - const char *symbol = sym_name(se); + const char *symbol = sym_entry_name(se); int len = se->len - 1; if (len < 8) @@ -813,8 +808,8 @@ static int compare_symbols(const void *a, const void *b) return wa - wb; /* sort by the number of prefix underscores */ - wa = strspn(sym_name(sa), "_"); - wb = strspn(sym_name(sb), "_"); + wa = strspn(sym_entry_name(sa), "_"); + wb = strspn(sym_entry_name(sb), "_"); if (wa != wb) return wa - wb; @@ -829,13 +824,14 @@ static void sort_symbols(void) int main(int argc, char **argv) { - const char *out_bin_name; - FILE *out_bin_file; + const char *in, *sysmap = NULL, *out_bin_name; + FILE *sysmap_out = NULL, *out_bin_file; while (1) { static const struct option long_options[] = { {"all-symbols", no_argument, &all_symbols, 1}, {"pc-relative", no_argument, &pc_relative, 1}, + {"sysmap", required_argument, NULL, 's'}, {}, }; @@ -843,13 +839,33 @@ int main(int argc, char **argv) if (c == -1) break; - if (c != 0) + if (c == 's') + sysmap = optarg; + else if (c != 0) usage(); } - if (optind + 2 != argc) + if (optind + 2 != argc && !(sysmap && optind + 1 == argc)) usage(); + in = argv[optind]; + if (sysmap) { + sysmap_out = fopen(sysmap, "w"); + if (!sysmap_out) { + perror(sysmap); + exit(EXIT_FAILURE); + } + } + + if (optind + 1 == argc) { + read_elf(in, sysmap_out); + if (ferror(sysmap_out) || fclose(sysmap_out)) { + perror(sysmap); + exit(EXIT_FAILURE); + } + return 0; + } + out_bin_name = argv[optind + 1]; out_bin_file = fopen(out_bin_name, "w"); if (!out_bin_file) { @@ -857,7 +873,11 @@ int main(int argc, char **argv) exit(EXIT_FAILURE); } - read_map(argv[optind]); + read_elf(in, sysmap_out); + if (sysmap_out && (ferror(sysmap_out) || fclose(sysmap_out))) { + perror(sysmap); + exit(EXIT_FAILURE); + } shrink_table(); sort_symbols(); optimize_token_table(); diff --git a/scripts/kallsyms.h b/scripts/kallsyms.h new file mode 100644 index 000000000000..4b2801ba6285 --- /dev/null +++ b/scripts/kallsyms.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef KALLSYMS_H +#define KALLSYMS_H + +#include +#include +#include +#include + +#include +#include "elf-parse.h" + +static inline bool string_starts_with(const char *s, const char *prefix) +{ + return strncmp(s, prefix, strlen(prefix)) == 0; +} + +static inline bool string_ends_with(const char *s, const char *suffix) +{ + size_t len = strlen(s), suffix_len = strlen(suffix); + + return len >= suffix_len && strcmp(s + len - suffix_len, suffix) == 0; +} + +/* A symbol as nm lists it. */ +struct sysmap_symbol { + unsigned long long addr; + const char *name; + char type; +}; + +/* The symbols of an ELF file that System.map lists, in its order. */ +struct sysmap { + struct sysmap_symbol *syms; + size_t nr_syms; + int addr_width; /* hex digits of an address */ + struct elf_file elf; /* the mapping the names point into */ +}; + +struct sysmap *sysmap_read(const char *path); +void sysmap_write(const struct sysmap *map, FILE *out); +void sysmap_free(struct sysmap *map); + +#endif /* KALLSYMS_H */ diff --git a/scripts/link-vmlinux.sh b/scripts/link-vmlinux.sh index ed5ff97c0667..c520741f85c2 100755 --- a/scripts/link-vmlinux.sh +++ b/scripts/link-vmlinux.sh @@ -123,11 +123,15 @@ kallsymso_changed() ! cmp -s "${kallsymso_prev}.sym" "${kallsymso}.sym" } -# Create ${2}.o file with all symbols from the ${1} object file +# Create ${2}.o with the kallsyms tables for ${1} (the vmlinux, or an empty +# listing for the first pass); list the symbols used in ${3} if given. kallsyms() { local kallsymopt; + if [ -n "${3:-}" ]; then + kallsymopt="--sysmap=${3}" + fi if is_enabled CONFIG_KALLSYMS_ALL; then kallsymopt="${kallsymopt} --all-symbols" fi @@ -150,18 +154,15 @@ kallsyms() # Perform kallsyms for the given temporary vmlinux. sysmap_and_kallsyms() { - mksysmap "${1}" "${1}.syms" - kallsyms "${1}.syms" "${1}.kallsyms" - + kallsyms "${1}" "${1}.kallsyms" "${1}.syms" kallsyms_sysmap=${1}.syms } # Create map file with all symbols from ${1} -# See mksymap for additional details mksysmap() { - info NM ${2} - ${NM} -n "${1}" | sed -f "${srctree}/scripts/mksysmap" > "${2}" + info SYSMAP ${2} + scripts/kallsyms --sysmap="${2}" "${1}" } sorttable() diff --git a/scripts/mksysmap b/scripts/mksysmap deleted file mode 100755 index 856b26ba2ac0..000000000000 --- a/scripts/mksysmap +++ /dev/null @@ -1,94 +0,0 @@ -#!/bin/sed -f -# SPDX-License-Identifier: GPL-2.0-only -# -# sed script to filter out symbols that are not needed for System.map, -# or not suitable for kallsyms. The input should be 'nm -n '. -# -# System.map is used by module-init tools and some debugging -# tools to retrieve the actual addresses of symbols in the kernel. -# -# readprofile starts reading symbols when _stext is found, and -# continue until it finds a symbol which is not either of 'T', 't', -# 'W' or 'w'. -# -# --------------------------------------------------------------------------- -# Ignored symbol types -# - -# a: local absolute symbols -# N: debugging symbols -# U: undefined global symbols -# w: local weak symbols -/ [aNUw] /d - -# --------------------------------------------------------------------------- -# Ignored prefixes -# (do not forget a space before each pattern) - -# local symbols for ARM, MIPS, etc. -/ \$/d - -# local labels, .LBB, .Ltmpxxx, .L__unnamed_xx, .LASANPC, etc. -/ \.L/d - -# arm64 EFI stub namespace -/ __efistub_/d - -# arm64 local symbols in PIE namespace -/ __pi_\$/d -/ __pi_\.L/d - -# arm64 local symbols in non-VHE KVM namespace -/ __kvm_nvhe_\$/d -/ __kvm_nvhe_\.L/d - -# lld arm/aarch64/mips thunks -/ __[[:alnum:]]*Thunk_/d - -# CFI type identifiers -/ __kcfi_typeid_/d -/ __kvm_nvhe___kcfi_typeid_/d -/ __pi___kcfi_typeid_/d - -# CRC from modversions -/ __crc_/d - -# EXPORT_SYMBOL (symbol name) -/ __kstrtab_/d - -# EXPORT_SYMBOL (namespace) -/ __kstrtabns_/d - -# MODULE_DEVICE_TABLE (symbol name) -/ __mod_device_table__/d - -# --------------------------------------------------------------------------- -# Ignored suffixes -# (do not forget '$' after each pattern) - -# arm -/_from_arm$/d -/_from_thumb$/d -/_veneer$/d - -# --------------------------------------------------------------------------- -# Ignored symbols (exact match) -# (do not forget a space before and '$' after each pattern) - -# for LoongArch? -/ L0$/d - -# ppc -/ _SDA_BASE_$/d -/ _SDA2_BASE_$/d - -# MODULE_INFO() -/ __UNIQUE_ID_modinfo_[0-9]*$/d - -# --------------------------------------------------------------------------- -# Ignored patterns -# (symbols that contain the pattern are ignored) - -# ppc stub -/\.long_branch\./d -/\.plt_branch\./d From f85147b291e355b34dfbe3c343c3682d488a8f07 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Wed, 23 Sep 2026 18:18:00 +0100 Subject: [PATCH 31/59] kbuild: move the toolchain checks into scripts/Kconfig.toolchain init/Kconfig opens with two hundred lines identifying the compiler, assembler, linker and rustc and probing which options they support, none of it to do with init. Move them into their own file, sourced from the top of init/Kconfig, so that the next commit can add to them without growing init/Kconfig further. Also update MAINTAINERS to put the new file in the KCONFIG entry. No functional change intended. Assisted-by: LLM Signed-off-by: Lorenzo Stoakes (ARM) Link: https://patch.msgid.link/20260923-build-speedup-v4-11-73128809a4a4@kernel.org Signed-off-by: Nathan Chancellor --- MAINTAINERS | 1 + init/Kconfig | 197 +------------------------------------ scripts/Kconfig.toolchain | 198 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 200 insertions(+), 196 deletions(-) create mode 100644 scripts/Kconfig.toolchain diff --git a/MAINTAINERS b/MAINTAINERS index 6215fcb07770..46c4f7e3061c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14057,6 +14057,7 @@ Q: https://patchwork.kernel.org/project/linux-kbuild/list/ T: git git://git.kernel.org/pub/scm/linux/kernel/git/kbuild/linux.git F: Documentation/kbuild/kconfig* F: scripts/Kconfig.include +F: scripts/Kconfig.toolchain F: scripts/kconfig/ KCORE diff --git a/init/Kconfig b/init/Kconfig index 8583d9f06c52..352601b44b5c 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -1,201 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only -config CC_VERSION_TEXT - string - default "$(CC_VERSION_TEXT)" - help - This is used in unclear ways: - - Re-run Kconfig when the compiler is updated - The 'default' property references the environment variable, - CC_VERSION_TEXT so it is recorded in include/config/auto.conf.cmd. - When the compiler is updated, Kconfig will be invoked. - - - Ensure full rebuild when the compiler is updated - include/linux/compiler-version.h contains this option in the comment - line so fixdep adds include/config/CC_VERSION_TEXT into the - auto-generated dependency. When the compiler is updated, syncconfig - will touch it and then every file will be rebuilt. - -config CC_IS_GCC - def_bool $(success,test "$(cc-name)" = GCC) - -config GCC_VERSION - int - default $(cc-version) if CC_IS_GCC - default 0 - -config CC_IS_CLANG - def_bool $(success,test "$(cc-name)" = Clang) - -config CLANG_VERSION - int - default $(cc-version) if CC_IS_CLANG - default 0 - -config AS_IS_GNU - def_bool $(success,test "$(as-name)" = GNU) - -config AS_IS_LLVM - def_bool $(success,test "$(as-name)" = LLVM) - -config AS_VERSION - int - # Use clang version if this is the integrated assembler - default CLANG_VERSION if AS_IS_LLVM - default $(as-version) - -config LD_IS_BFD - def_bool $(success,test "$(ld-name)" = BFD) - -config LD_VERSION - int - default $(ld-version) if LD_IS_BFD - default 0 - -config LD_IS_LLD - def_bool $(success,test "$(ld-name)" = LLD) - -config LLD_VERSION - int - default $(ld-version) if LD_IS_LLD - default 0 - -config RUSTC_VERSION - int - default $(rustc-version) - help - It does not depend on `RUST` since that one may need to use the version - in a `depends on`. - -config RUST_IS_AVAILABLE - def_bool $(success,$(srctree)/scripts/rust_is_available.sh) - help - This shows whether a suitable Rust toolchain is available (found). - - Please see Documentation/rust/quick-start.rst for instructions on how - to satisfy the build requirements of Rust support. - - In particular, the Makefile target 'rustavailable' is useful to check - why the Rust toolchain is not being detected. - -config RUSTC_LLVM_VERSION - int - default $(rustc-llvm-version) - -config RUSTC_LLVM_MAJOR_VERSION - int - default $(shell,expr $(rustc-llvm-version) / 10000) - -config RUSTC_CLANG_LLVM_COMPATIBLE - bool - default y if CC_IS_CLANG && RUSTC_LLVM_MAJOR_VERSION = $(shell,expr $(cc-version) / 10000) - help - This indicates whether Rust and Clang use LLVM of the same major - version. - - Operations involving handling LLVM IR or bitcode (e.g. cross-language - LTO) require the same LLVM major version to work properly. For best - compatibility it is recommended that the exact same LLVM is used. - -config ARCH_HAS_CC_CAN_LINK - bool - -config CC_CAN_LINK - bool - default ARCH_CC_CAN_LINK if ARCH_HAS_CC_CAN_LINK - default $(cc_can_link_user,$(m64-flag)) if 64BIT - default $(cc_can_link_user,$(m32-flag)) - -# Fixed in GCC 14, 13.3, 12.4 and 11.5 -# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=113921 -config GCC_ASM_GOTO_OUTPUT_BROKEN - bool - depends on CC_IS_GCC - default y if GCC_VERSION < 110500 - default y if GCC_VERSION >= 120000 && GCC_VERSION < 120400 - default y if GCC_VERSION >= 130000 && GCC_VERSION < 130300 - -config CC_HAS_ASM_GOTO_OUTPUT - def_bool y - depends on !GCC_ASM_GOTO_OUTPUT_BROKEN - depends on $(success,echo 'int foo(int x) { asm goto ("": "=r"(x) ::: bar); return x; bar: return 0; }' | $(CC) -x c - -c -o /dev/null) - -config CC_HAS_ASM_GOTO_TIED_OUTPUT - depends on CC_HAS_ASM_GOTO_OUTPUT - # Detect buggy gcc and clang, fixed in gcc-11 clang-14. - def_bool $(success,echo 'int foo(int *x) { asm goto (".long (%l[bar]) - .": "+m"(*x) ::: bar); return *x; bar: return 0; }' | $CC -x c - -c -o /dev/null) - -config TOOLS_SUPPORT_RELR - def_bool $(success,env "CC=$(CC)" "LD=$(LD)" "NM=$(NM)" "OBJCOPY=$(OBJCOPY)" $(srctree)/scripts/tools-support-relr.sh) - -config CC_HAS_ASM_INLINE - def_bool $(success,echo 'void foo(void) { asm inline (""); }' | $(CC) -x c - -c -o /dev/null) - -config CC_HAS_ASSUME - bool - # clang needs to be at least 19.1.0 since the meaning of the assume - # attribute changed: - # https://github.com/llvm/llvm-project/commit/c44fa3e8a9a44c2e9a575768a3c185354b9f6c17 - default y if CC_IS_CLANG && CLANG_VERSION >= 190100 - # supported since gcc 13.1.0 - # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106654 - default y if CC_IS_GCC && GCC_VERSION >= 130100 - -config CC_HAS_NO_PROFILE_FN_ATTR - def_bool $(success,echo '__attribute__((no_profile_instrument_function)) int x();' | $(CC) -x c - -c -o /dev/null -Werror) - -config CC_HAS_COUNTED_BY - bool - # clang needs to be at least 20.1.0 to avoid potential crashes - # when building structures that contain __counted_by - # https://github.com/ClangBuiltLinux/linux/issues/2114 - # https://github.com/llvm/llvm-project/commit/160fb1121cdf703c3ef5e61fb26c5659eb581489 - default y if CC_IS_CLANG && CLANG_VERSION >= 200100 - # supported since gcc 15.1.0 - # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108896 - default y if CC_IS_GCC && GCC_VERSION >= 150100 - -config CC_HAS_COUNTED_BY_PTR - bool - # supported since clang 22 - default y if CC_IS_CLANG && CLANG_VERSION >= 220100 - # supported since gcc 16.0.0 - default y if CC_IS_GCC && GCC_VERSION >= 160000 - -config CC_HAS_BROKEN_COUNTED_BY_REF - bool - # https://github.com/llvm/llvm-project/issues/182575 - default y if CC_IS_CLANG && CLANG_VERSION < 220100 - -config CC_HAS_ALLOC_TOKEN - def_bool $(cc-option,-falloc-token-max=123) - -config CC_HAS_MULTIDIMENSIONAL_NONSTRING - def_bool $(success,echo 'char tag[][4] __attribute__((__nonstring__)) = { };' | $(CC) $(CLANG_FLAGS) -x c - -c -o /dev/null -Werror) - -config LD_CAN_USE_KEEP_IN_OVERLAY - # ld.lld prior to 21.0.0 did not support KEEP within an overlay description - # https://github.com/llvm/llvm-project/pull/130661 - def_bool LD_IS_BFD || LLD_VERSION >= 210000 - -config RUSTC_HAS_SPAN_FILE - def_bool RUSTC_VERSION >= 108800 - -config RUSTC_HAS_UNNECESSARY_TRANSMUTES - def_bool RUSTC_VERSION >= 108800 - -config RUSTC_HAS_FILE_WITH_NUL - def_bool RUSTC_VERSION >= 108900 - -config RUSTC_HAS_FILE_AS_C_STR - def_bool RUSTC_VERSION >= 109100 - -config RUSTC_HAS_SUSPICIOUS_RUNTIME_SYMBOL_DEFINITIONS - def_bool RUSTC_VERSION >= 109800 - -config PAHOLE_VERSION - int - default "$(PAHOLE_VERSION)" +source "scripts/Kconfig.toolchain" config CONSTRUCTORS bool diff --git a/scripts/Kconfig.toolchain b/scripts/Kconfig.toolchain new file mode 100644 index 000000000000..fac36246451f --- /dev/null +++ b/scripts/Kconfig.toolchain @@ -0,0 +1,198 @@ +# SPDX-License-Identifier: GPL-2.0-only +config CC_VERSION_TEXT + string + default "$(CC_VERSION_TEXT)" + help + This is used in unclear ways: + + - Re-run Kconfig when the compiler is updated + The 'default' property references the environment variable, + CC_VERSION_TEXT so it is recorded in include/config/auto.conf.cmd. + When the compiler is updated, Kconfig will be invoked. + + - Ensure full rebuild when the compiler is updated + include/linux/compiler-version.h contains this option in the comment + line so fixdep adds include/config/CC_VERSION_TEXT into the + auto-generated dependency. When the compiler is updated, syncconfig + will touch it and then every file will be rebuilt. + +config CC_IS_GCC + def_bool $(success,test "$(cc-name)" = GCC) + +config GCC_VERSION + int + default $(cc-version) if CC_IS_GCC + default 0 + +config CC_IS_CLANG + def_bool $(success,test "$(cc-name)" = Clang) + +config CLANG_VERSION + int + default $(cc-version) if CC_IS_CLANG + default 0 + +config AS_IS_GNU + def_bool $(success,test "$(as-name)" = GNU) + +config AS_IS_LLVM + def_bool $(success,test "$(as-name)" = LLVM) + +config AS_VERSION + int + # Use clang version if this is the integrated assembler + default CLANG_VERSION if AS_IS_LLVM + default $(as-version) + +config LD_IS_BFD + def_bool $(success,test "$(ld-name)" = BFD) + +config LD_VERSION + int + default $(ld-version) if LD_IS_BFD + default 0 + +config LD_IS_LLD + def_bool $(success,test "$(ld-name)" = LLD) + +config LLD_VERSION + int + default $(ld-version) if LD_IS_LLD + default 0 + +config RUSTC_VERSION + int + default $(rustc-version) + help + It does not depend on `RUST` since that one may need to use the version + in a `depends on`. + +config RUST_IS_AVAILABLE + def_bool $(success,$(srctree)/scripts/rust_is_available.sh) + help + This shows whether a suitable Rust toolchain is available (found). + + Please see Documentation/rust/quick-start.rst for instructions on how + to satisfy the build requirements of Rust support. + + In particular, the Makefile target 'rustavailable' is useful to check + why the Rust toolchain is not being detected. + +config RUSTC_LLVM_VERSION + int + default $(rustc-llvm-version) + +config RUSTC_LLVM_MAJOR_VERSION + int + default $(shell,expr $(rustc-llvm-version) / 10000) + +config RUSTC_CLANG_LLVM_COMPATIBLE + bool + default y if CC_IS_CLANG && RUSTC_LLVM_MAJOR_VERSION = $(shell,expr $(cc-version) / 10000) + help + This indicates whether Rust and Clang use LLVM of the same major + version. + + Operations involving handling LLVM IR or bitcode (e.g. cross-language + LTO) require the same LLVM major version to work properly. For best + compatibility it is recommended that the exact same LLVM is used. + +config ARCH_HAS_CC_CAN_LINK + bool + +config CC_CAN_LINK + bool + default ARCH_CC_CAN_LINK if ARCH_HAS_CC_CAN_LINK + default $(cc_can_link_user,$(m64-flag)) if 64BIT + default $(cc_can_link_user,$(m32-flag)) + +# Fixed in GCC 14, 13.3, 12.4 and 11.5 +# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=113921 +config GCC_ASM_GOTO_OUTPUT_BROKEN + bool + depends on CC_IS_GCC + default y if GCC_VERSION < 110500 + default y if GCC_VERSION >= 120000 && GCC_VERSION < 120400 + default y if GCC_VERSION >= 130000 && GCC_VERSION < 130300 + +config CC_HAS_ASM_GOTO_OUTPUT + def_bool y + depends on !GCC_ASM_GOTO_OUTPUT_BROKEN + depends on $(success,echo 'int foo(int x) { asm goto ("": "=r"(x) ::: bar); return x; bar: return 0; }' | $(CC) -x c - -c -o /dev/null) + +config CC_HAS_ASM_GOTO_TIED_OUTPUT + depends on CC_HAS_ASM_GOTO_OUTPUT + # Detect buggy gcc and clang, fixed in gcc-11 clang-14. + def_bool $(success,echo 'int foo(int *x) { asm goto (".long (%l[bar]) - .": "+m"(*x) ::: bar); return *x; bar: return 0; }' | $CC -x c - -c -o /dev/null) + +config TOOLS_SUPPORT_RELR + def_bool $(success,env "CC=$(CC)" "LD=$(LD)" "NM=$(NM)" "OBJCOPY=$(OBJCOPY)" $(srctree)/scripts/tools-support-relr.sh) + +config CC_HAS_ASM_INLINE + def_bool $(success,echo 'void foo(void) { asm inline (""); }' | $(CC) -x c - -c -o /dev/null) + +config CC_HAS_ASSUME + bool + # clang needs to be at least 19.1.0 since the meaning of the assume + # attribute changed: + # https://github.com/llvm/llvm-project/commit/c44fa3e8a9a44c2e9a575768a3c185354b9f6c17 + default y if CC_IS_CLANG && CLANG_VERSION >= 190100 + # supported since gcc 13.1.0 + # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106654 + default y if CC_IS_GCC && GCC_VERSION >= 130100 + +config CC_HAS_NO_PROFILE_FN_ATTR + def_bool $(success,echo '__attribute__((no_profile_instrument_function)) int x();' | $(CC) -x c - -c -o /dev/null -Werror) + +config CC_HAS_COUNTED_BY + bool + # clang needs to be at least 20.1.0 to avoid potential crashes + # when building structures that contain __counted_by + # https://github.com/ClangBuiltLinux/linux/issues/2114 + # https://github.com/llvm/llvm-project/commit/160fb1121cdf703c3ef5e61fb26c5659eb581489 + default y if CC_IS_CLANG && CLANG_VERSION >= 200100 + # supported since gcc 15.1.0 + # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108896 + default y if CC_IS_GCC && GCC_VERSION >= 150100 + +config CC_HAS_COUNTED_BY_PTR + bool + # supported since clang 22 + default y if CC_IS_CLANG && CLANG_VERSION >= 220100 + # supported since gcc 16.0.0 + default y if CC_IS_GCC && GCC_VERSION >= 160000 + +config CC_HAS_BROKEN_COUNTED_BY_REF + bool + # https://github.com/llvm/llvm-project/issues/182575 + default y if CC_IS_CLANG && CLANG_VERSION < 220100 + +config CC_HAS_ALLOC_TOKEN + def_bool $(cc-option,-falloc-token-max=123) + +config CC_HAS_MULTIDIMENSIONAL_NONSTRING + def_bool $(success,echo 'char tag[][4] __attribute__((__nonstring__)) = { };' | $(CC) $(CLANG_FLAGS) -x c - -c -o /dev/null -Werror) + +config LD_CAN_USE_KEEP_IN_OVERLAY + # ld.lld prior to 21.0.0 did not support KEEP within an overlay description + # https://github.com/llvm/llvm-project/pull/130661 + def_bool LD_IS_BFD || LLD_VERSION >= 210000 + +config RUSTC_HAS_SPAN_FILE + def_bool RUSTC_VERSION >= 108800 + +config RUSTC_HAS_UNNECESSARY_TRANSMUTES + def_bool RUSTC_VERSION >= 108800 + +config RUSTC_HAS_FILE_WITH_NUL + def_bool RUSTC_VERSION >= 108900 + +config RUSTC_HAS_FILE_AS_C_STR + def_bool RUSTC_VERSION >= 109100 + +config RUSTC_HAS_SUSPICIOUS_RUNTIME_SYMBOL_DEFINITIONS + def_bool RUSTC_VERSION >= 109800 + +config PAHOLE_VERSION + int + default "$(PAHOLE_VERSION)" From 454e00cf8ee5aff555ad5865b8c3dbf3731afcc9 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Wed, 23 Sep 2026 18:18:01 +0100 Subject: [PATCH 32/59] kbuild: avoid re-running compiler and linker probes Each kernel make invocation begins with ~30 compiler and linker runs each of which performs duplicate probe for a number of compiler and linker options. This is useless work - the compiler and its version is known, so use these to determine which options are available, once. Using the newly introduced cc-option-str, track the result of each probe in CC_OPT_*, LD_OPT_* variables and have Makefile's simply append the symbol. Some care has to be taken around negative options, i.e. -Wno-xxx, as gcc accepts an unknown -Wno- without complaint and only complains later, so instead probe the positive form for these cases. Use CC_HAS_* For those probes that gate a Makefile conditional rather than a simple option, as per the established pattern for this. This change avoids a subtle issue with ppc32 (big endian) - clang doesn't accept -fno-stack-clash-protection on this sub-target, so keep this option in the Makefile. With gcc and clang, defconfig and allmodconfig, the recorded command lines are unchanged, a build with nothing to do rebuilds nothing and W=1 continues to function correctly. Doing this improves all builds, but has a particularly positive impact on no-op builds (builds where nothing has changed). Whole build, 128-thread Threadripper 9980X, best of N runs: before after delta ------------------------------- x86 defconfig, no-op make, gcc 0.78s 0.60s -0.18s (-23%) x86 defconfig, no-op make, clang 0.94s 0.66s -0.28s (-30%) x86 defconfig, touch mm/vma.c, gcc 8.6s 8.5s -0.16s (-2%) x86 defconfig, touch mm/vma.c, clang 8.0s 7.7s -0.24s (-3%) x86 defconfig, clean, clang 29.8s 29.4s -0.37s (-1%) x86 allmodconfig, no-op make, gcc 12.9s 12.7s -0.23s (-2%) x86 allmodconfig, no-op make, clang 14.2s 13.9s -0.29s (-2%) x86 allmodconfig, touch mm/vma.c, clang 32.2s 31.9s -0.30s (-1%) Assisted-by: LLM Signed-off-by: Lorenzo Stoakes (ARM) Link: https://patch.msgid.link/20260923-build-speedup-v4-12-73128809a4a4@kernel.org Signed-off-by: Nathan Chancellor --- Makefile | 23 +++++---- arch/arm64/kernel/pi/Makefile | 2 +- arch/riscv/kernel/pi/Makefile | 2 +- arch/x86/Kconfig | 17 ++++++ arch/x86/Makefile | 12 ++--- drivers/firmware/efi/libstub/Makefile | 2 +- scripts/Kconfig.toolchain | 74 +++++++++++++++++++++++++++ scripts/Makefile.warn | 28 +++++----- 8 files changed, 126 insertions(+), 34 deletions(-) diff --git a/Makefile b/Makefile index ab3ba78376f7..fab2bf3759a7 100644 --- a/Makefile +++ b/Makefile @@ -946,8 +946,8 @@ KBUILD_RUSTFLAGS += -Coverflow-checks=$(if $(CONFIG_RUST_OVERFLOW_CHECKS),y,n) ifdef CONFIG_CC_IS_GCC # gcc-10 renamed --param=allow-store-data-races=0 to # -fno-allow-store-data-races. -KBUILD_CFLAGS += $(call cc-option,--param=allow-store-data-races=0) -KBUILD_CFLAGS += $(call cc-option,-fno-allow-store-data-races) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_ALLOW_STORE_DATA_RACES_PARAM) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_NO_ALLOW_STORE_DATA_RACES) endif ifdef CONFIG_READABLE_ASM @@ -1011,18 +1011,20 @@ endif endif # Explicitly clear padding bits during variable initialization -KBUILD_CFLAGS += $(call cc-option,-fzero-init-padding-bits=all) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_ZERO_INIT_PADDING_BITS) # While VLAs have been removed, GCC produces unreachable stack probes # for the randomize_kstack_offset feature. Disable it for all compilers. +# Probed here rather than in Kconfig as clang only accepts it for some +# sub-targets, so the architecture's flags matter. KBUILD_CFLAGS += $(call cc-option, -fno-stack-clash-protection) # Get details on warnings generated due to GCC value tracking. -KBUILD_CFLAGS += $(call cc-option, -fdiagnostics-show-context=2) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_DIAGNOSTICS_SHOW_CONTEXT) # Show inlining notes for __attribute__((warning/error)) call chains. # GCC supports this unconditionally while Clang 23+ provides a flag. -KBUILD_CFLAGS += $(call cc-option, -fdiagnostics-show-inlining-chain) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_DIAGNOSTICS_SHOW_INLINING_CHAIN) # Clear used registers at func exit (to reduce data lifetime and ROP gadgets). ifdef CONFIG_ZERO_CALL_USED_REGS @@ -1033,7 +1035,7 @@ ifdef CONFIG_FUNCTION_TRACER ifdef CONFIG_FTRACE_MCOUNT_USE_CC CC_FLAGS_FTRACE += -mrecord-mcount ifdef CONFIG_HAVE_NOP_MCOUNT - ifeq ($(call cc-option-yn, -mnop-mcount),y) + ifdef CONFIG_CC_HAS_MNOP_MCOUNT CC_FLAGS_FTRACE += -mnop-mcount CC_FLAGS_USING += -DCC_USING_NOP_MCOUNT endif @@ -1051,8 +1053,7 @@ ifdef CONFIG_FTRACE_MCOUNT_USE_RECORDMCOUNT endif endif ifdef CONFIG_HAVE_FENTRY - # s390-linux-gnu-gcc did not support -mfentry until gcc-9. - ifeq ($(call cc-option-yn, -mfentry),y) + ifdef CONFIG_CC_HAS_MFENTRY CC_FLAGS_FTRACE += -mfentry CC_FLAGS_USING += -DCC_USING_FENTRY endif @@ -1160,7 +1161,7 @@ NOSTDINC_FLAGS += -nostdinc # the kernel uses only C99 flexible arrays for dynamically sized trailing # arrays. Enforce this for everything that may examine structure sizes and # perform bounds checking. -KBUILD_CFLAGS += $(call cc-option, -fstrict-flex-arrays=3) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_STRICT_FLEX_ARRAYS) # disable invalid "can't wrap" optimizations for signed / pointers KBUILD_CFLAGS += -fno-strict-overflow @@ -1243,11 +1244,11 @@ LDFLAGS_vmlinux += --build-id=sha1 # COMDAT-deduplicated sections. Use --force-group-allocation to resolve these # groups when linking modules. The option is available from ld.bfd 2.29 and # ld.lld 19.1.0. -KBUILD_LDFLAGS_MODULE += $(call ld-option,--force-group-allocation) +KBUILD_LDFLAGS_MODULE += $(CONFIG_LD_OPT_FORCE_GROUP_ALLOCATION) KBUILD_LDFLAGS += -z noexecstack ifeq ($(CONFIG_LD_IS_BFD),y) -KBUILD_LDFLAGS += $(call ld-option,--no-warn-rwx-segments) +KBUILD_LDFLAGS += $(CONFIG_LD_OPT_NO_WARN_RWX_SEGMENTS) endif ifeq ($(CONFIG_STRIP_ASM_SYMS),y) diff --git a/arch/arm64/kernel/pi/Makefile b/arch/arm64/kernel/pi/Makefile index be92d73c25b2..4a0cbedfdcd6 100644 --- a/arch/arm64/kernel/pi/Makefile +++ b/arch/arm64/kernel/pi/Makefile @@ -9,7 +9,7 @@ KBUILD_CFLAGS := $(subst $(CC_FLAGS_FTRACE),,$(KBUILD_CFLAGS)) -fpie \ -include $(srctree)/include/linux/hidden.h \ -D__DISABLE_EXPORTS -ffreestanding -D__NO_FORTIFY \ -fno-asynchronous-unwind-tables -fno-unwind-tables \ - $(call cc-option,-fno-addrsig) + $(CONFIG_CC_OPT_NO_ADDRSIG) # this code may run with the MMU off so disable unaligned accesses CFLAGS_map_range.o += -mstrict-align diff --git a/arch/riscv/kernel/pi/Makefile b/arch/riscv/kernel/pi/Makefile index bc098edac898..4c8e52b3f176 100644 --- a/arch/riscv/kernel/pi/Makefile +++ b/arch/riscv/kernel/pi/Makefile @@ -8,7 +8,7 @@ KBUILD_CFLAGS := $(subst $(CC_FLAGS_FTRACE),,$(KBUILD_CFLAGS)) -fpie \ -include $(srctree)/include/linux/hidden.h \ -D__DISABLE_EXPORTS -ffreestanding \ -fno-asynchronous-unwind-tables -fno-unwind-tables \ - $(call cc-option,-fno-addrsig) + $(CONFIG_CC_OPT_NO_ADDRSIG) # Disable LTO KBUILD_CFLAGS := $(filter-out $(CC_FLAGS_LTO), $(KBUILD_CFLAGS)) diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index 15fd9ec5ecac..0fea2fc4ba1d 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -2364,6 +2364,23 @@ config CC_HAS_KCFI_ARITY def_bool $(cc-option,-fsanitize=kcfi -fsanitize-kcfi-arity) depends on CC_IS_CLANG && !RUST +config CC_OPT_INDIRECT_BRANCH_CS_PREFIX + def_string "$(cc-option-str,-mindirect-branch-cs-prefix)" + +config CC_OPT_CF_PROTECTION_NONE + def_string "$(cc-option-str,-fcf-protection=none)" + +# with jump tables off: the compilers use NOTRACK for them, which kernel IBT +# does not allow +config CC_OPT_CF_PROTECTION_BRANCH + def_string "$(cc-option-str,-fcf-protection=branch -fno-jump-tables)" + +config CC_OPT_FALIGN_JUMPS + def_string "$(cc-option-str,-falign-jumps=1)" + +config CC_OPT_FALIGN_LOOPS + def_string "$(cc-option-str,-falign-loops=1)" + config FUNCTION_PADDING_CFI int default 59 if FUNCTION_ALIGNMENT_64B diff --git a/arch/x86/Makefile b/arch/x86/Makefile index 598f178102ee..aac30ba51b2f 100644 --- a/arch/x86/Makefile +++ b/arch/x86/Makefile @@ -20,7 +20,7 @@ ifdef CONFIG_CC_IS_CLANG RETPOLINE_CFLAGS := -mretpoline-external-thunk RETPOLINE_VDSO_CFLAGS := -mretpoline endif -RETPOLINE_CFLAGS += $(call cc-option,-mindirect-branch-cs-prefix) +RETPOLINE_CFLAGS += $(CONFIG_CC_OPT_INDIRECT_BRANCH_CS_PREFIX) ifdef CONFIG_MITIGATION_RETHUNK RETHUNK_CFLAGS := -mfunction-return=thunk-extern @@ -52,7 +52,7 @@ REALMODE_CFLAGS := $(CC_FLAGS_DIALECT) -m16 -g -Os \ -DDISABLE_BRANCH_PROFILING -D__DISABLE_EXPORTS \ -Wall -Wstrict-prototypes -march=i386 -mregparm=3 \ -fno-strict-aliasing -fomit-frame-pointer -fno-pic \ - -mno-mmx -mno-sse $(call cc-option,-fcf-protection=none) + -mno-mmx -mno-sse $(CONFIG_CC_OPT_CF_PROTECTION_NONE) REALMODE_CFLAGS += -ffreestanding REALMODE_CFLAGS += -fno-stack-protector @@ -99,10 +99,10 @@ ifeq ($(CONFIG_X86_KERNEL_IBT),y) # # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=104816 # -KBUILD_CFLAGS += $(call cc-option,-fcf-protection=branch -fno-jump-tables) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_CF_PROTECTION_BRANCH) KBUILD_RUSTFLAGS += -Zcf-protection=branch $(if $(call rustc-min-version,109300),-Cjump-tables=n,-Zno-jump-tables) else -KBUILD_CFLAGS += $(call cc-option,-fcf-protection=none) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_CF_PROTECTION_NONE) endif ifeq ($(CONFIG_X86_32),y) @@ -138,10 +138,10 @@ else KBUILD_CFLAGS += -m64 # Align jump targets to 1 byte, not the default 16 bytes: - KBUILD_CFLAGS += $(call cc-option,-falign-jumps=1) + KBUILD_CFLAGS += $(CONFIG_CC_OPT_FALIGN_JUMPS) # Pack loops tightly as well: - KBUILD_CFLAGS += $(call cc-option,-falign-loops=1) + KBUILD_CFLAGS += $(CONFIG_CC_OPT_FALIGN_LOOPS) # Don't autogenerate traditional x87 instructions KBUILD_CFLAGS += -mno-80387 diff --git a/drivers/firmware/efi/libstub/Makefile b/drivers/firmware/efi/libstub/Makefile index 77a2b2d74f3f..415f63105b48 100644 --- a/drivers/firmware/efi/libstub/Makefile +++ b/drivers/firmware/efi/libstub/Makefile @@ -40,7 +40,7 @@ KBUILD_CFLAGS := $(subst $(CC_FLAGS_FTRACE),,$(cflags-y)) \ -D__NO_FORTIFY \ -ffreestanding \ -fno-stack-protector \ - $(call cc-option,-fno-addrsig) \ + $(CONFIG_CC_OPT_NO_ADDRSIG) \ -D__DISABLE_EXPORTS # diff --git a/scripts/Kconfig.toolchain b/scripts/Kconfig.toolchain index fac36246451f..d708a175fe48 100644 --- a/scripts/Kconfig.toolchain +++ b/scripts/Kconfig.toolchain @@ -144,6 +144,80 @@ config CC_HAS_ASSUME config CC_HAS_NO_PROFILE_FN_ATTR def_bool $(success,echo '__attribute__((no_profile_instrument_function)) int x();' | $(CC) -x c - -c -o /dev/null -Werror) +config CC_OPT_ZERO_INIT_PADDING_BITS + def_string "$(cc-option-str,-fzero-init-padding-bits=all)" + +config CC_OPT_NO_ADDRSIG + def_string "$(cc-option-str,-fno-addrsig)" + +config CC_OPT_DIAGNOSTICS_SHOW_CONTEXT + def_string "$(cc-option-str,-fdiagnostics-show-context=2)" + +config CC_OPT_DIAGNOSTICS_SHOW_INLINING_CHAIN + def_string "$(cc-option-str,-fdiagnostics-show-inlining-chain)" + +config CC_OPT_STRICT_FLEX_ARRAYS + def_string "$(cc-option-str,-fstrict-flex-arrays=3)" + +# gcc-10 renamed --param=allow-store-data-races=0 to -fno-allow-store-data-races +config CC_OPT_ALLOW_STORE_DATA_RACES_PARAM + def_string "$(cc-option-str,--param=allow-store-data-races=0)" if CC_IS_GCC + +config CC_OPT_NO_ALLOW_STORE_DATA_RACES + def_string "$(cc-option-str,-fno-allow-store-data-races)" if CC_IS_GCC + +config CC_HAS_MNOP_MCOUNT + def_bool $(cc-option,-mnop-mcount) + +# s390-linux-gnu-gcc did not support -mfentry until gcc-9. +config CC_HAS_MFENTRY + def_bool $(cc-option,-mfentry) + +config LD_OPT_FORCE_GROUP_ALLOCATION + def_string "$(ld-option-str,--force-group-allocation)" + +config LD_OPT_NO_WARN_RWX_SEGMENTS + def_string "$(ld-option-str,--no-warn-rwx-segments)" + +config CC_OPT_WNO_ADDRESS_OF_PACKED_MEMBER + def_string "-Wno-address-of-packed-member" if $(cc-option,-Waddress-of-packed-member) + +config CC_OPT_WNO_FORMAT_OVERFLOW_NON_KPRINTF + def_string "-Wno-format-overflow-non-kprintf" if CC_IS_CLANG && $(cc-option,-Wformat-overflow-non-kprintf) + +config CC_OPT_WNO_FORMAT_TRUNCATION_NON_KPRINTF + def_string "-Wno-format-truncation-non-kprintf" if CC_IS_CLANG && $(cc-option,-Wformat-truncation-non-kprintf) + +config CC_OPT_WNO_DEFAULT_CONST_INIT_UNSAFE + def_string "-Wno-default-const-init-unsafe" if CC_IS_CLANG && $(cc-option,-Wdefault-const-init-unsafe) + +config CC_OPT_WNO_DANGLING_POINTER + def_string "-Wno-dangling-pointer" if $(cc-option,-Wdangling-pointer) + +config CC_OPT_WVLA_LARGER_THAN + def_string "$(cc-option-str,-Wvla-larger-than=1)" + +config CC_HAS_WSTRINGOP_OVERFLOW + def_bool $(cc-option,-Wstringop-overflow) + +config CC_OPT_WNO_UNTERMINATED_STRING_INITIALIZATION + def_string "-Wno-unterminated-string-initialization" if $(cc-option,-Wunterminated-string-initialization) + +config CC_OPT_WERROR_DESIGNATED_INIT + def_string "$(cc-option-str,-Werror=designated-init)" + +config CC_OPT_WENUM_CONVERSION + def_string "$(cc-option-str,-Wenum-conversion)" + +config CC_OPT_WNO_PACKED_NOT_ALIGNED + def_string "-Wno-packed-not-aligned" if $(cc-option,-Wpacked-not-aligned) + +config CC_OPT_WNO_FORMAT_OVERFLOW + def_string "-Wno-format-overflow" if $(cc-option,-Wformat-overflow) + +config CC_OPT_WNO_STRINGOP_TRUNCATION + def_string "-Wno-stringop-truncation" if $(cc-option,-Wstringop-truncation) + config CC_HAS_COUNTED_BY bool # clang needs to be at least 20.1.0 to avoid potential crashes diff --git a/scripts/Makefile.warn b/scripts/Makefile.warn index 35af7d6c6d18..556fa249ca07 100644 --- a/scripts/Makefile.warn +++ b/scripts/Makefile.warn @@ -17,7 +17,7 @@ KBUILD_CFLAGS += -Werror=strict-prototypes KBUILD_CFLAGS += -Wno-format-security KBUILD_CFLAGS += -Wno-trigraphs KBUILD_CFLAGS += -Wno-frame-address -KBUILD_CFLAGS += $(call cc-option, -Wno-address-of-packed-member) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_WNO_ADDRESS_OF_PACKED_MEMBER) KBUILD_CFLAGS += -Wmissing-declarations KBUILD_CFLAGS += -Wmissing-prototypes @@ -30,8 +30,8 @@ KBUILD_CFLAGS-$(CONFIG_CC_NO_ARRAY_BOUNDS) += -Wno-array-bounds ifdef CONFIG_CC_IS_CLANG # Clang checks for overflow/truncation with '%p', while GCC does not: # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=111219 -KBUILD_CFLAGS += $(call cc-option, -Wno-format-overflow-non-kprintf) -KBUILD_CFLAGS += $(call cc-option, -Wno-format-truncation-non-kprintf) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_WNO_FORMAT_OVERFLOW_NON_KPRINTF) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_WNO_FORMAT_TRUNCATION_NON_KPRINTF) # Clang may emit a warning when a const variable, such as the dummy variables # in typecheck(), or const member of an aggregate type are not initialized, @@ -43,7 +43,7 @@ KBUILD_CFLAGS += $(call cc-option, -Wno-format-truncation-non-kprintf) # disabled with this same switch, there should not be too much coverage lost # because -Wuninitialized will still flag when an uninitialized const variable # is used. -KBUILD_CFLAGS += $(call cc-option, -Wno-default-const-init-unsafe) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_WNO_DEFAULT_CONST_INIT_UNSAFE) else # gcc inanely warns about local variables called 'main' @@ -54,7 +54,7 @@ endif KBUILD_CFLAGS += -Wno-type-limits # These result in bogus false positives -KBUILD_CFLAGS += $(call cc-option, -Wno-dangling-pointer) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_WNO_DANGLING_POINTER) # Stack Variable Length Arrays (VLAs) must not be used in the kernel. # Function array parameters should, however, be usable, but -Wvla will @@ -62,7 +62,7 @@ KBUILD_CFLAGS += $(call cc-option, -Wno-dangling-pointer) # types, so depend on GCC for now to keep stack VLAs out of the tree. # https://github.com/llvm/llvm-project/issues/57098 # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=98217 -KBUILD_CFLAGS += $(call cc-option,-Wvla-larger-than=1) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_WVLA_LARGER_THAN) # disable pointer signed / unsigned warnings in gcc 4.0 KBUILD_CFLAGS += -Wno-pointer-sign @@ -73,11 +73,11 @@ KBUILD_CFLAGS += -Wno-pointer-sign KBUILD_CFLAGS += -Wcast-function-type # Currently, disable -Wstringop-overflow for GCC 11, globally. -KBUILD_CFLAGS-$(CONFIG_CC_NO_STRINGOP_OVERFLOW) += $(call cc-option, -Wno-stringop-overflow) -KBUILD_CFLAGS-$(CONFIG_CC_STRINGOP_OVERFLOW) += $(call cc-option, -Wstringop-overflow) +KBUILD_CFLAGS-$(CONFIG_CC_NO_STRINGOP_OVERFLOW) += $(if $(CONFIG_CC_HAS_WSTRINGOP_OVERFLOW),-Wno-stringop-overflow) +KBUILD_CFLAGS-$(CONFIG_CC_STRINGOP_OVERFLOW) += $(if $(CONFIG_CC_HAS_WSTRINGOP_OVERFLOW),-Wstringop-overflow) # Currently, disable -Wunterminated-string-initialization as broken -KBUILD_CFLAGS += $(call cc-option, -Wno-unterminated-string-initialization) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_WNO_UNTERMINATED_STRING_INITIALIZATION) # The allocators already balk at large sizes, so silence the compiler # warnings for bounds checks involving those possible values. While @@ -100,10 +100,10 @@ KBUILD_CFLAGS += -Werror=date-time KBUILD_CFLAGS += -Werror=incompatible-pointer-types # Require designated initializers for all marked structures -KBUILD_CFLAGS += $(call cc-option,-Werror=designated-init) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_WERROR_DESIGNATED_INIT) # Warn if there is an enum types mismatch -KBUILD_CFLAGS += $(call cc-option,-Wenum-conversion) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_WENUM_CONVERSION) KBUILD_CFLAGS += -Wunused @@ -125,12 +125,12 @@ else # Suppress them by using -Wno... except for W=1. KBUILD_CFLAGS += -Wno-unused-but-set-variable KBUILD_CFLAGS += -Wno-unused-const-variable -KBUILD_CFLAGS += $(call cc-option, -Wno-packed-not-aligned) -KBUILD_CFLAGS += $(call cc-option, -Wno-format-overflow) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_WNO_PACKED_NOT_ALIGNED) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_WNO_FORMAT_OVERFLOW) ifdef CONFIG_CC_IS_GCC KBUILD_CFLAGS += -Wno-format-truncation endif -KBUILD_CFLAGS += $(call cc-option, -Wno-stringop-truncation) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_WNO_STRINGOP_TRUNCATION) KBUILD_CFLAGS += -Wno-override-init # alias for -Wno-initializer-overrides in clang From 48ebe1cbbeb51879ba4d22775fee1654d94d1cc6 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Wed, 23 Sep 2026 18:18:11 +0100 Subject: [PATCH 33/59] kbuild: compress the kernel with pigz if available The gzip step of a kernel build is very lengthy, especially for larger builds such as allmodconfig. gzip itself cannot be run in parallel, however an alternative tool exists that can, providing the same feature set as gzip itself - pigz - which works as a drop-in replacement. On a 128-core Threadripper, gzip -9 of a 36 MiB x86-64 vmlinux.bin takes 1.6s, and with pigz it takes 0.09s, so the performance increase is significant. Therefore introduce the KPGZIP build variable which specifies the compressor to use for the kernel image and default it to pigz if available, or KGZIP otherwise (which itself defaults to gzip). Modules, packaging and the embedded kernel configuration (CONFIG_IKCONFIG) continue to use KGZIP which defaults to a single-threaded gzip instance. Module compression is run in parallel by make, so pigz offers no benefit there, packaging sits outside the build and the embedded configuration is too small to matter. pigz is executed using scripts/jobserver-exec, which reserves the job slots make has free - necessary, as otherwise pigz would just grab unbounded CPUs. The kernel image compression step is always performed at the end of the build so should typically have all job slots available. pigz always provides byte-for-byte identical output across runs regardless of thread count, but pigz and gzip are not guaranteed to generate the same binary output, so reproducible builds must use the same tools consistently. Also update Documentation/kbuild/kbuild.rst to describe KPGZIP and Documentation/process/changes.rst to list the optional pigz dependency. While there, add entries for the other compressor programs. Finally, update scripts/ver_linux to include the pigz version there. Every x86 build ends with the compression of vmlinux.bin, 36MB for defconfig and over 200MB for allmodconfig, no-op builds are unchanged. Whole build, 128-thread Threadripper 9980X, best of N runs: before after delta ------------------------------- x86 defconfig, touch mm/vma.c, gcc 7.7s 6.0s -1.7s (-22%) x86 defconfig, touch mm/vma.c, clang 6.9s 5.4s -1.5s (-21%) x86 defconfig, clean, gcc 28.2s 26.3s -1.8s (-6%) x86 defconfig, clean, clang 28.7s 27.2s -1.5s (-5%) x86 allmodconfig, touch mm/vma.c, gcc 23.7s 15.4s -8.3s (-35%) x86 allmodconfig, touch mm/vma.c, clang 22.5s 15.6s -7.0s (-31%) x86 allmodconfig, clean, gcc 294.1s 278.6s -15.5s (-5%) x86 allmodconfig, clean, clang 283.9s 266.8s -17.1s (-6%) Suggested-by: Kees Cook Link: https://zlib.net/pigz/ Assisted-by: LLM Reviewed-by: Manuel Ebner Reviewed-by: Kees Cook Signed-off-by: Lorenzo Stoakes (ARM) Link: https://patch.msgid.link/20260923-build-speedup-v4-22-73128809a4a4@kernel.org Signed-off-by: Nathan Chancellor --- Documentation/kbuild/kbuild.rst | 17 +++++++++++++++++ Documentation/process/changes.rst | 8 ++++++++ Makefile | 5 ++++- kernel/Makefile | 6 +++++- scripts/Makefile.lib | 4 ++-- scripts/jobserver-exec | 3 ++- scripts/ver_linux | 1 + tools/lib/python/jobserver.py | 13 +++++++++++++ 8 files changed, 52 insertions(+), 5 deletions(-) diff --git a/Documentation/kbuild/kbuild.rst b/Documentation/kbuild/kbuild.rst index 5a9013bacfb7..e71a692a559a 100644 --- a/Documentation/kbuild/kbuild.rst +++ b/Documentation/kbuild/kbuild.rst @@ -110,6 +110,23 @@ HOSTLDLIBS ---------- Additional libraries to link against when building host programs. +KGZIP +----- +The gzip program used for compressed modules, packaging and the embedded +kernel configuration. Defaults to gzip. + +KPGZIP +------ +The gzip program used for the compressed kernel image. Defaults to pigz (a +parallel implementation of gzip), run under scripts/jobserver-exec so that it +uses the job slots make has free, if pigz is installed, otherwise to +``KGZIP``. Set ``KPGZIP=`` on the make command line to use another program. + +KBZIP2, KLZOP, LZMA, LZ4, XZ, ZSTD +---------------------------------- +The compressor programs for the other formats. Each defaults to the program +of the same name. + .. _userkbuildflags: USERCFLAGS diff --git a/Documentation/process/changes.rst b/Documentation/process/changes.rst index 0aa232b117b5..28f2ca63318e 100644 --- a/Documentation/process/changes.rst +++ b/Documentation/process/changes.rst @@ -55,6 +55,7 @@ nfs-utils 1.0.5 showmount --version openssl & libcrypto 1.0.0 openssl version pahole 1.26 pahole --version pcmciautils 004 pccardctl -V +pigz (optional) 2.4 pigz --version PPP 2.4.0 pppd --version procps 3.2.0 ps --version Python 3.9.x python3 --version @@ -206,6 +207,13 @@ GNU AWK GNU AWK is needed if you want kernel builds to generate address range data for builtin modules (CONFIG_BUILTIN_MODULE_RANGES). +pigz (optional) +--------------- + +pigz is a parallel implementation of gzip. If it is installed the compressed +kernel image is produced with it rather than with gzip, see ``KPGZIP`` in +Documentation/kbuild/kbuild.rst. + System utilities **************** diff --git a/Makefile b/Makefile index fab2bf3759a7..7f2d5cc6e5a2 100644 --- a/Makefile +++ b/Makefile @@ -568,6 +568,9 @@ LZMA = lzma LZ4 = lz4 XZ = xz ZSTD = zstd +# The kernel image is compressed with pigz, on the job slots make has free, +# if it is installed. Everything else uses KGZIP. +KPGZIP := $(if $(shell command -v pigz 2>/dev/null),$(PYTHON3) $(abs_srctree)/scripts/jobserver-exec pigz -p %PARALLELISM%,$(KGZIP)) TAR = tar CHECKFLAGS := -D__linux__ -Dlinux -D__STDC__ -Dunix -D__unix__ \ @@ -648,7 +651,7 @@ export RUSTC RUSTDOC RUSTFMT RUSTC_OR_CLIPPY_QUIET RUSTC_OR_CLIPPY BINDGEN LLVM_ export HOSTRUSTC KBUILD_HOSTRUSTFLAGS export CPP AR NM STRIP OBJCOPY OBJDUMP READELF PAHOLE RESOLVE_BTFIDS LEX YACC AWK INSTALLKERNEL export PERL PYTHON3 CHECK CHECKFLAGS MAKE UTS_MACHINE HOSTCXX -export KGZIP KBZIP2 KLZOP LZMA LZ4 XZ ZSTD TAR +export KGZIP KPGZIP KBZIP2 KLZOP LZMA LZ4 XZ ZSTD TAR export KBUILD_HOSTCXXFLAGS KBUILD_HOSTLDFLAGS KBUILD_HOSTLDLIBS KBUILD_PROCMACROLDFLAGS LDFLAGS_MODULE export KBUILD_USERCFLAGS KBUILD_USERLDFLAGS diff --git a/kernel/Makefile b/kernel/Makefile index 1e1a31673577..c64c82c96b40 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -153,9 +153,13 @@ obj-$(CONFIG_SCF_TORTURE_TEST) += scftorture.o $(obj)/configs.o: $(obj)/config_data.gz +# .config is small, so it is compressed with gzip rather than the image compressor. +quiet_cmd_gzip_config = GZIP $@ + cmd_gzip_config = cat $(real-prereqs) | $(KGZIP) -n -f -9 > $@ + targets += config_data config_data.gz $(obj)/config_data.gz: $(obj)/config_data FORCE - $(call if_changed,gzip) + $(call if_changed,gzip_config) filechk_cat = cat $< diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib index 0a4fdd8bd975..19e96b30a122 100644 --- a/scripts/Makefile.lib +++ b/scripts/Makefile.lib @@ -333,11 +333,11 @@ quiet_cmd_ar = AR $@ quiet_cmd_objcopy = OBJCOPY $@ cmd_objcopy = $(OBJCOPY) $(OBJCOPYFLAGS) $(OBJCOPYFLAGS_$(@F)) $< $@ -# Gzip +# Gzip, for the kernel image # --------------------------------------------------------------------------- quiet_cmd_gzip = GZIP $@ - cmd_gzip = cat $(real-prereqs) | $(KGZIP) -n -f -9 > $@ + cmd_gzip = cat $(real-prereqs) | $(KPGZIP) -n -f -9 > $@ # Bzip2 # --------------------------------------------------------------------------- diff --git a/scripts/jobserver-exec b/scripts/jobserver-exec index 21b319e6c9a5..89119acb1b9e 100755 --- a/scripts/jobserver-exec +++ b/scripts/jobserver-exec @@ -4,7 +4,8 @@ """ Determines how many parallel tasks "make" is expecting, as it is not exposed via any special variables, reserves them all, runs a subprocess -with PARALLELISM environment variable set, and releases the jobs back again. +with PARALLELISM environment variable set and any %PARALLELISM% in its +arguments replaced by the count, and releases the jobs back again. See: https://www.gnu.org/software/make/manual/html_node/POSIX-Jobserver.html#POSIX-Jobserver diff --git a/scripts/ver_linux b/scripts/ver_linux index 00bdaf30d590..e06d7c650710 100755 --- a/scripts/ver_linux +++ b/scripts/ver_linux @@ -57,6 +57,7 @@ BEGIN { printversion("pahole", version("pahole --version")) printversion("Pcmcia-cs", version("cardmgr -V")) printversion("pcmciautils", version("pccardctl -V")) + printversion("pigz", version("pigz --version")) printversion("PPP", version("pppd --version")) printversion("procps", version("ps --version")) printversion("Python", version("python3 -V")) diff --git a/tools/lib/python/jobserver.py b/tools/lib/python/jobserver.py index 0b1ffdf9f7a3..7e94635bf7d9 100755 --- a/tools/lib/python/jobserver.py +++ b/tools/lib/python/jobserver.py @@ -29,6 +29,10 @@ $claim child to do the actual work. The end goal here is to keep the total number of build tasks under the limit established by the initial ``make -j$n_proc`` call. +A command that takes its thread count on the command line rather than from +the environment can use the ``%PARALLELISM%`` token in its arguments, which +is replaced by the count, or by 1 when there is no jobserver. + See: https://www.gnu.org/software/make/manual/html_node/POSIX-Jobserver.html#POSIX-Jobserver """ @@ -38,6 +42,8 @@ import os import subprocess import sys +PARALLELISM_TOKEN = "%PARALLELISM%" + def warn(text, *args): print(f'WARNING: {text}', *args, file = sys.stderr) @@ -182,6 +188,9 @@ class JobserverExec: Run a command setting PARALLELISM env variable to the number of available job slots (claim) + 1, e.g. it will reserve claim slots to do the actual build work, plus one to monitor its children. + + Any %PARALLELISM% in the command's arguments is replaced by the same + number, or by 1 when there is no jobserver. """ self.open() # Ensure that self.claim is set @@ -192,4 +201,8 @@ class JobserverExec: if self.claim: os.environ["PARALLELISM"] = str(self.claim) + if not isinstance(cmd, str): + parallelism = str(self.claim or 1) + cmd = [arg.replace(PARALLELISM_TOKEN, parallelism) for arg in cmd] + return subprocess.call(cmd, *args, **pwargs) From a98db7418797bc27bef53c861b97ac748f598414 Mon Sep 17 00:00:00 2001 From: Sasha Levin Date: Sun, 27 Sep 2026 12:12:27 -0400 Subject: [PATCH 34/59] kbuild: keep .modinfo when vmlinux is linked with --gc-sections Builds with CONFIG_LD_DEAD_CODE_DATA_ELIMINATION=y, such as arm allmodconfig, now fail while extracting modules.builtin.modinfo: arm-linux-gnueabihf-objcopy: vmlinux.unstripped: can't dump section '.modinfo' - it does not exist: file format not recognized /bin/sh: 1: cannot open modules.builtin.modinfo: No such file or, with LLVM: llvm-objcopy: error: 'vmlinux.unstripped': section '.modinfo' not found Nothing references the MODULE_INFO() strings, so --gc-sections discards every .modinfo input section and vmlinux.unstripped ends up with no .modinfo data. This is not new: ever since modules.builtin.modinfo started being extracted from vmlinux.unstripped, these builds have silently produced an empty file, losing the modinfo of every built-in module. An arm multi_v7_defconfig build with LD_DEAD_CODE_DATA_ELIMINATION on current mainline yields a 0 byte modules.builtin.modinfo. The switch to --dump-section only turned that into a build error. Wrap the input section in KEEP() so that the linker retains it. The output section is still (INFO), so nothing is allocated for it. With the same arm config, modules.builtin.modinfo now has 14119 entries, and on x86_64 defconfig, where nothing is garbage collected, it is byte-identical before and after this change. Found by KernelCI builds of the linus-next tree. Fixes: 39cfd5b12160 ("kbuild: extract modules.builtin.modinfo from vmlinux.unstripped") Fixes: 5fe4dd596641 ("kbuild: do not allocate .modinfo in vmlinux") Assisted-by: LLM Signed-off-by: Sasha Levin Link: https://patch.msgid.link/20260927161227.1179863-1-sashal@kernel.org Signed-off-by: Nathan Chancellor --- include/asm-generic/vmlinux.lds.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h index a6730d34e8c6..7a3e9f00d628 100644 --- a/include/asm-generic/vmlinux.lds.h +++ b/include/asm-generic/vmlinux.lds.h @@ -855,7 +855,7 @@ KLP_SYMID #define MODINFO \ - .modinfo (INFO) : { *(.modinfo) } + .modinfo (INFO) : { KEEP(*(.modinfo)) } #ifdef CONFIG_GENERIC_BUG #define BUG_TABLE \ From 14e2c1f5431a03478ed4bbf2e72298c769f375ac Mon Sep 17 00:00:00 2001 From: Nicolas Schier Date: Mon, 28 Sep 2026 19:26:57 +0200 Subject: [PATCH 35/59] kbuild: Mark usr_gen_init_cpio as no-dot-config-target Let usr/gen_init_cpio be built also if there is no kernel configuration. Askar Safin reported that gen_init_cpio may be of use before any kernel configuration is available. Add usr_gen_init_cpio to list of targets that do not require a .config file. Reported-by: Askar Safin Closes: https://lore.kernel.org/all/20260220191150.244006-1-safinaskar@gmail.com Fixes: f8e05c10631e ("kbuild: Add top-level target for building gen_init_cpio") Signed-off-by: Nicolas Schier Link: https://patch.msgid.link/20260928-move-gen_init_cpio-to-scripts-v4-1-f437a33c40ac@kernel.org Signed-off-by: Nathan Chancellor --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index f6745d723f6d..c52fffe8042f 100644 --- a/Makefile +++ b/Makefile @@ -298,6 +298,7 @@ no-dot-config-targets := $(clean-targets) \ %asm-generic kernelversion %src-pkg dt_binding_check \ dt_style_selftest \ outputmakefile rustavailable rustfmt rustfmtcheck \ + usr_gen_init_cpio \ run-command no-sync-config-targets := $(no-dot-config-targets) %install modules_sign kernelrelease \ image_name From 33bef82263082e81406ef5662de2900e24038a78 Mon Sep 17 00:00:00 2001 From: Nicolas Schier Date: Mon, 28 Sep 2026 19:26:58 +0200 Subject: [PATCH 36/59] kbuild: Move gen_init_cpio and gen_initramfs.sh to scripts/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gen_init_cpio and gen_initramfs.sh are part of kbuild and required for all kernel builds w/ CONFIG_BLK_DEV_INITRD. Move both to scripts/ to be more clear about their importance. Link: https://lore.kernel.org/all/aSdrCFkUQup3qb-q@derry.ads.avm.de/ Reviewed-by: Thomas Weißschuh Reviewed-by: Nathan Chancellor Signed-off-by: Nicolas Schier Link: https://patch.msgid.link/20260928-move-gen_init_cpio-to-scripts-v4-2-f437a33c40ac@kernel.org Signed-off-by: Nathan Chancellor --- .../early-userspace/early_userspace_support.rst | 16 ++++++++-------- .../filesystems/ramfs-rootfs-initramfs.rst | 10 +++++----- MAINTAINERS | 1 + Makefile | 10 +++++----- scripts/.gitignore | 1 + scripts/Makefile | 3 +++ scripts/Makefile.package | 2 +- {usr => scripts}/gen_init_cpio.c | 0 {usr => scripts}/gen_initramfs.sh | 4 ++-- scripts/remove-stale-files | 2 ++ .../sbom/tests/cmd_graph/test_savedcmd_parser.py | 2 +- tools/testing/selftests/kho/vmtest.sh | 2 +- tools/testing/selftests/liveupdate/vmtest.sh | 4 ++-- tools/testing/selftests/nolibc/Makefile.nolibc | 2 +- usr/.gitignore | 4 +++- usr/Kconfig | 2 +- usr/Makefile | 4 +--- 17 files changed, 38 insertions(+), 31 deletions(-) rename {usr => scripts}/gen_init_cpio.c (100%) rename {usr => scripts}/gen_initramfs.sh (97%) diff --git a/Documentation/driver-api/early-userspace/early_userspace_support.rst b/Documentation/driver-api/early-userspace/early_userspace_support.rst index 60d1e1bc9413..1070f82e3578 100644 --- a/Documentation/driver-api/early-userspace/early_userspace_support.rst +++ b/Documentation/driver-api/early-userspace/early_userspace_support.rst @@ -55,9 +55,9 @@ user root (0). INITRAMFS_ROOT_GID can be set to a group ID that needs to be mapped to group root (0). A source file must be directives in the format required by the -usr/gen_init_cpio utility (run 'usr/gen_init_cpio -h' to get the +scripts/gen_init_cpio utility (run 'scripts/gen_init_cpio -h' to get the file format). The directives in the file will be passed directly to -usr/gen_init_cpio. +scripts/gen_init_cpio. When a combination of directories and files are specified then the initramfs image will be an aggregate of all of them. In this way a user @@ -69,17 +69,17 @@ early userspace image can be built by an unprivileged user. As a technical note, when directories and files are specified, the entire CONFIG_INITRAMFS_SOURCE is passed to -usr/gen_initramfs.sh. This means that CONFIG_INITRAMFS_SOURCE +scripts/gen_initramfs.sh. This means that CONFIG_INITRAMFS_SOURCE can really be interpreted as any legal argument to gen_initramfs.sh. If a directory is specified as an argument then the contents are scanned, uid/gid translation is performed, and -usr/gen_init_cpio file directives are output. If a file is -specified as an argument to usr/gen_initramfs.sh then the +scripts/gen_init_cpio file directives are output. If a file is +specified as an argument to scripts/gen_initramfs.sh then the contents of the file are simply copied to the output. All of the output directives from directory scanning and file contents copying are -processed by usr/gen_init_cpio. +processed by scripts/gen_init_cpio. -See also 'usr/gen_initramfs.sh -h'. +See also 'scripts/gen_initramfs.sh -h'. Where's this all leading? ========================= @@ -142,7 +142,7 @@ b) some device and filesystem drivers built as modules and stored in an c) using initramfs. The call to prepare_namespace() must be skipped. This means that a binary must do all the work. Said binary can be stored - into initramfs either via modifying usr/gen_init_cpio.c or via the new + into initramfs either via modifying scripts/gen_init_cpio.c or via the new initrd format, an cpio archive. It must be called "/init". This binary is responsible to do all the things prepare_namespace() would do. diff --git a/Documentation/filesystems/ramfs-rootfs-initramfs.rst b/Documentation/filesystems/ramfs-rootfs-initramfs.rst index 165117a721ce..4263fe06115d 100644 --- a/Documentation/filesystems/ramfs-rootfs-initramfs.rst +++ b/Documentation/filesystems/ramfs-rootfs-initramfs.rst @@ -155,7 +155,7 @@ specification such as the following example:: dir /mnt 755 0 0 file /init initramfs/init.sh 755 0 0 -Run "usr/gen_init_cpio" (after the kernel build) to get a usage message +Run "scripts/gen_init_cpio" (after the kernel build) to get a usage message documenting the above file format. One advantage of the configuration file is that root access is not required to @@ -167,9 +167,9 @@ Documentation/driver-api/early-userspace/early_userspace_support.rst for more de The kernel does not depend on external cpio tools. If you specify a directory instead of a configuration file, the kernel's build infrastructure creates a configuration file from that directory (usr/Makefile calls -usr/gen_initramfs.sh), and proceeds to package up that directory -using the config file (by feeding it to usr/gen_init_cpio, which is created -from usr/gen_init_cpio.c). The kernel's build-time cpio creation code is +scripts/gen_initramfs.sh), and proceeds to package up that directory +using the config file (by feeding it to scripts/gen_init_cpio, which is created +from scripts/gen_init_cpio.c). The kernel's build-time cpio creation code is entirely self-contained, and the kernel's boot-time extractor is also (obviously) self-contained. @@ -313,7 +313,7 @@ the above threads) is: 2) The cpio archive format chosen by the kernel is simpler and cleaner (and thus easier to create and parse) than any of the (literally dozens of) various tar archive formats. The complete initramfs archive format is - explained in buffer-format.rst, created in usr/gen_init_cpio.c, and + explained in buffer-format.rst, created in scripts/gen_init_cpio.c, and extracted in init/initramfs.c. All three together come to less than 26k total of human-readable text. diff --git a/MAINTAINERS b/MAINTAINERS index 6215fcb07770..7704147dd975 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14138,6 +14138,7 @@ F: scripts/basic/ F: scripts/clang-tools/ F: scripts/container F: scripts/dummy-tools/ +F: scripts/gen_init* F: scripts/include/ F: scripts/install.sh F: scripts/mk* diff --git a/Makefile b/Makefile index c52fffe8042f..c496d1639103 100644 --- a/Makefile +++ b/Makefile @@ -298,7 +298,7 @@ no-dot-config-targets := $(clean-targets) \ %asm-generic kernelversion %src-pkg dt_binding_check \ dt_style_selftest \ outputmakefile rustavailable rustfmt rustfmtcheck \ - usr_gen_init_cpio \ + scripts_gen_init_cpio \ run-command no-sync-config-targets := $(no-dot-config-targets) %install modules_sign kernelrelease \ image_name @@ -1532,9 +1532,9 @@ ifdef CONFIG_HEADERS_INSTALL prepare: headers endif -PHONY += usr_gen_init_cpio -usr_gen_init_cpio: scripts_basic - $(Q)$(MAKE) $(build)=usr usr/gen_init_cpio +PHONY += scripts_gen_init_cpio +scripts_gen_init_cpio: scripts_basic + $(Q)$(MAKE) $(build)=scripts scripts/gen_init_cpio PHONY += scripts_unifdef scripts_unifdef: scripts_basic @@ -1835,7 +1835,7 @@ distclean: mrproper # Packaging of the kernel to various formats # --------------------------------------------------------------------------- -modules-cpio-pkg: usr_gen_init_cpio +modules-cpio-pkg: scripts_gen_init_cpio %src-pkg: FORCE $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.package $@ diff --git a/scripts/.gitignore b/scripts/.gitignore index 4215c2208f7e..700456543910 100644 --- a/scripts/.gitignore +++ b/scripts/.gitignore @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only /asn1_compiler +/gen_init_cpio /gen_packed_field_checks /generate_rust_target /insert-sys-cert diff --git a/scripts/Makefile b/scripts/Makefile index 3434a82a119f..3d50805b0885 100644 --- a/scripts/Makefile +++ b/scripts/Makefile @@ -53,6 +53,9 @@ ifdef CONFIG_BUILDTIME_MCOUNT_SORT HOSTCFLAGS_sorttable.o += -DMCOUNT_SORT_ENABLED endif +hostprogs += gen_init_cpio +always-$(CONFIG_BLK_DEV_INITRD) += gen_init_cpio + # The following programs are only built on demand hostprogs += unifdef gen_packed_field_checks diff --git a/scripts/Makefile.package b/scripts/Makefile.package index 6d36786ba31c..948ad5b679d1 100644 --- a/scripts/Makefile.package +++ b/scripts/Makefile.package @@ -198,7 +198,7 @@ tar%-pkg: linux-$(KERNELRELEASE)-$(ARCH).tar.% FORCE $(Q)$(MAKE) -f $(srctree)/Makefile INSTALL_MOD_PATH=$@/$(INSTALL_MOD_PATH) modules_install quiet_cmd_cpio = CPIO $@ - cmd_cpio = $(CONFIG_SHELL) $(srctree)/usr/gen_initramfs.sh -o $@ $< + cmd_cpio = $(CONFIG_SHELL) $(srctree)/scripts/gen_initramfs.sh -o $@ $< modules-$(KERNELRELEASE)-$(ARCH).cpio: .tmp_modules_cpio $(call cmd,cpio) diff --git a/usr/gen_init_cpio.c b/scripts/gen_init_cpio.c similarity index 100% rename from usr/gen_init_cpio.c rename to scripts/gen_init_cpio.c diff --git a/usr/gen_initramfs.sh b/scripts/gen_initramfs.sh similarity index 97% rename from usr/gen_initramfs.sh rename to scripts/gen_initramfs.sh index 7eba2fddf0ef..9c6168a9338d 100755 --- a/usr/gen_initramfs.sh +++ b/scripts/gen_initramfs.sh @@ -6,7 +6,7 @@ # # Generate a cpio packed initramfs. It uses gen_init_cpio to generate # the cpio archive. -# This script assumes that gen_init_cpio is located in usr/ directory +# This script assumes that gen_init_cpio is located in scripts/ directory # error out on errors set -e @@ -247,4 +247,4 @@ done # If output_file is set we will generate cpio archive # we are careful to delete tmp files -usr/gen_init_cpio $output $timestamp $cpio_list +scripts/gen_init_cpio $output $timestamp $cpio_list diff --git a/scripts/remove-stale-files b/scripts/remove-stale-files index 6e39fa8540df..e46337e4bfb8 100755 --- a/scripts/remove-stale-files +++ b/scripts/remove-stale-files @@ -26,3 +26,5 @@ rm -f scripts/selinux/genheaders/genheaders rm -f *.spec rm -f lib/test_fortify.log + +rm -f usr/gen_init_cpio diff --git a/scripts/sbom/tests/cmd_graph/test_savedcmd_parser.py b/scripts/sbom/tests/cmd_graph/test_savedcmd_parser.py index d7776f072e03..93787a1cd156 100644 --- a/scripts/sbom/tests/cmd_graph/test_savedcmd_parser.py +++ b/scripts/sbom/tests/cmd_graph/test_savedcmd_parser.py @@ -338,7 +338,7 @@ class TestSavedCmdParser(unittest.TestCase): # gen_initramfs.sh command tests def test_gen_initramfs(self): - cmd = "sh ../usr/gen_initramfs.sh -o usr/initramfs_data.cpio -l usr/.initramfs_data.cpio.d ../usr/default_cpio_list" + cmd = "sh ../scripts/gen_initramfs.sh -o usr/initramfs_data.cpio -l usr/.initramfs_data.cpio.d ../usr/default_cpio_list" expected = "../usr/default_cpio_list" self._assert_parsing(cmd, expected) diff --git a/tools/testing/selftests/kho/vmtest.sh b/tools/testing/selftests/kho/vmtest.sh index 0014bd76e88d..1e86ffcc3a79 100755 --- a/tools/testing/selftests/kho/vmtest.sh +++ b/tools/testing/selftests/kho/vmtest.sh @@ -100,7 +100,7 @@ file /init $tmp_dir/init 0755 0 0 file /kernel $kernel 0644 0 0 EOF - "$build_dir/usr/gen_init_cpio" "$tmp_dir/cpio_list" > "$initrd" + "$build_dir/scripts/gen_init_cpio" "$tmp_dir/cpio_list" > "$initrd" } function run_qemu() { diff --git a/tools/testing/selftests/liveupdate/vmtest.sh b/tools/testing/selftests/liveupdate/vmtest.sh index 64fd2ab55b76..672a9e9d685a 100755 --- a/tools/testing/selftests/liveupdate/vmtest.sh +++ b/tools/testing/selftests/liveupdate/vmtest.sh @@ -152,7 +152,7 @@ file /test_binary $workspace_dir/test_binary 0755 0 0 EOF # Generate inner_initrd.cpio - "$build_dir/usr/gen_init_cpio" "$workspace_dir/cpio_list_inner" > "$workspace_dir/inner_initrd.cpio" + "$build_dir/scripts/gen_init_cpio" "$workspace_dir/cpio_list_inner" > "$workspace_dir/inner_initrd.cpio" cat > "$workspace_dir/cpio_list" < "$initrd" + "$build_dir/scripts/gen_init_cpio" "$workspace_dir/cpio_list" > "$initrd" } function run_qemu() { diff --git a/tools/testing/selftests/nolibc/Makefile.nolibc b/tools/testing/selftests/nolibc/Makefile.nolibc index f70c8dfca018..380200b152e4 100644 --- a/tools/testing/selftests/nolibc/Makefile.nolibc +++ b/tools/testing/selftests/nolibc/Makefile.nolibc @@ -295,7 +295,7 @@ run-user: nolibc-test $(Q)$(REPORT) $(CURDIR)/run.out initramfs.cpio: kernel nolibc-test - $(QUIET_GEN)echo 'file /init nolibc-test 755 0 0' | $(objtree)/usr/gen_init_cpio - > initramfs.cpio + $(QUIET_GEN)echo 'file /init nolibc-test 755 0 0' | $(objtree)/scripts/gen_init_cpio - > initramfs.cpio initramfs: nolibc-test $(QUIET_MKDIR)mkdir -p initramfs diff --git a/usr/.gitignore b/usr/.gitignore index 8996e7a88902..13bf7a38355c 100644 --- a/usr/.gitignore +++ b/usr/.gitignore @@ -1,4 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only -/gen_init_cpio /initramfs_data.cpio /initramfs_inc_data + +# Keep around until cleanup in scripts/remove-stale-files +/gen_init_cpio diff --git a/usr/Kconfig b/usr/Kconfig index 9279a2893ab0..f87dd4bac418 100644 --- a/usr/Kconfig +++ b/usr/Kconfig @@ -13,7 +13,7 @@ config INITRAMFS_SOURCE to be used as an initramfs image. Directories should contain a filesystem layout to be included in the initramfs image. Files should contain entries according to the format described by the - "usr/gen_init_cpio" program in the kernel tree. + "scripts/gen_init_cpio" program in the kernel tree. When multiple directories and files are specified then the initramfs image will be the aggregate of all of them. diff --git a/usr/Makefile b/usr/Makefile index e8f42478a0b7..5b97ee66c00a 100644 --- a/usr/Makefile +++ b/usr/Makefile @@ -16,8 +16,6 @@ obj-$(CONFIG_BLK_DEV_INITRD) := initramfs_data.o $(obj)/initramfs_data.o: $(obj)/initramfs_inc_data -hostprogs := gen_init_cpio - ramfs-input := $(CONFIG_INITRAMFS_SOURCE) cpio-data := @@ -72,7 +70,7 @@ quiet_cmd_initfs = GEN $@ # 2) There are changes in which files are included (added or deleted) # 3) If gen_init_cpio are newer than initramfs_data.cpio # 4) Arguments to gen_initramfs.sh changes -$(obj)/initramfs_data.cpio: $(src)/gen_initramfs.sh $(obj)/gen_init_cpio $(deps_initramfs) FORCE +$(obj)/initramfs_data.cpio: $(srctree)/scripts/gen_initramfs.sh scripts/gen_init_cpio $(deps_initramfs) FORCE $(call if_changed,initfs) endif From 8688f0b29d021cad8188b11d5664b4a1f595691b Mon Sep 17 00:00:00 2001 From: Nicolas Schier Date: Tue, 29 Sep 2026 09:59:44 +0200 Subject: [PATCH 37/59] kconfig: tests: Reset all KCONFIG_* env variables by default Reset all environment variables with names starting with 'KCONFIG_' from the user-provided environment. This fixes the 'warn_changed_input' test on systems with KCONFIG_WARN_CHANGED_INPUT=1 and ensures a cleaner test environment. Fixes: 645323a7f4e5 ("kconfig: add optional warnings for changed input values") Signed-off-by: Nicolas Schier Reviewed-by: Julian Braha Link: https://patch.msgid.link/20260929-kconfig-tests-minor-updates-v2-1-c5902bee1d0c@fritz.com Signed-off-by: Nathan Chancellor --- scripts/kconfig/tests/conftest.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/scripts/kconfig/tests/conftest.py b/scripts/kconfig/tests/conftest.py index 2263bd2a3b3a..6fb391ac8fa9 100644 --- a/scripts/kconfig/tests/conftest.py +++ b/scripts/kconfig/tests/conftest.py @@ -57,9 +57,12 @@ class Conf: # Override 'srctree' environment to make the test as the top directory extra_env['srctree'] = self._test_dir - # Clear KCONFIG_DEFCONFIG_LIST to keep unit tests from being affected - # by the user's environment. - extra_env['KCONFIG_DEFCONFIG_LIST'] = '' + # Clear all KCONFIG_* environment variables + env = { + varname: os.environ[varname] + for varname in os.environ.keys() + if not varname.startswith("KCONFIG_") + } # Run Kconfig in a temporary directory. # This directory is automatically removed when done. @@ -75,7 +78,7 @@ class Conf: stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=temp_dir, - env=dict(os.environ, **extra_env)) + env=dict(env, **extra_env)) # If input key sequence is given, feed it to stdin. if in_keys: From 09bd92ba15bd00d937deea38a10a439458393043 Mon Sep 17 00:00:00 2001 From: Nicolas Schier Date: Tue, 29 Sep 2026 09:59:45 +0200 Subject: [PATCH 38/59] kconfig: tests: Provide defconfig outfile for savedefconfig Let kconfig test framework's 'savedefconfig' forward the requested output 'defconfig' filename to 'conf'. The original implementation was calling 'conf' with '--savedefconfig' but missed the mandatory filename for the defconfig output file. While at it, honour the 'dot_config' argument which points to the input config file. Fixes: 022a4bf6b59d ("kconfig: tests: add framework for Kconfig unit testing") Reviewed-by: Julian Braha Signed-off-by: Nicolas Schier Link: https://patch.msgid.link/20260929-kconfig-tests-minor-updates-v2-2-c5902bee1d0c@fritz.com Signed-off-by: Nathan Chancellor --- scripts/kconfig/tests/conftest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/kconfig/tests/conftest.py b/scripts/kconfig/tests/conftest.py index 6fb391ac8fa9..1f25de43128d 100644 --- a/scripts/kconfig/tests/conftest.py +++ b/scripts/kconfig/tests/conftest.py @@ -235,7 +235,8 @@ class Conf: dot_config: .config file for input returncode: exit status of the Kconfig executable """ - return self._run_conf('--savedefconfig', out_file='defconfig') + return self._run_conf('--savedefconfig={}'.format(out_file), + dot_config=dot_config, out_file='defconfig') def listnewconfig(self, dot_config=None): """Run listnewconfig. From ecc9c2b15a04b9bdbbc61eab738d685a2fb0dcd8 Mon Sep 17 00:00:00 2001 From: Nicolas Schier Date: Tue, 29 Sep 2026 09:59:46 +0200 Subject: [PATCH 39/59] kconfig: tests: warn_changed_input: Simplify by reusing the extra env Define the extra environment once and re-use it for the respective _run_conf() calls to increase readability. Reviewed-by: Julian Braha Signed-off-by: Nicolas Schier Link: https://patch.msgid.link/20260929-kconfig-tests-minor-updates-v2-3-c5902bee1d0c@fritz.com Signed-off-by: Nathan Chancellor --- .../kconfig/tests/warn_changed_input/__init__.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/scripts/kconfig/tests/warn_changed_input/__init__.py b/scripts/kconfig/tests/warn_changed_input/__init__.py index 4c3bca6af846..a5f07d4a882c 100644 --- a/scripts/kconfig/tests/warn_changed_input/__init__.py +++ b/scripts/kconfig/tests/warn_changed_input/__init__.py @@ -8,26 +8,24 @@ KCONFIG_WARN_CHANGED_INPUT is enabled. def test(conf): + warn_changed_input = { + "KCONFIG_WARN_CHANGED_INPUT": "1", + } + assert conf.olddefconfig('config') == 0 assert 'user-provided values changed by Kconfig' not in conf.stderr assert conf._run_conf('--olddefconfig', dot_config='config', - extra_env={ - 'KCONFIG_WARN_CHANGED_INPUT': '1', - }) == 0 + extra_env=warn_changed_input) == 0 assert conf.stderr_contains('expected_stderr') assert conf.config_matches('expected_config') assert conf._run_conf('--olddefconfig', dot_config='config', - extra_env={ - 'KCONFIG_WARN_CHANGED_INPUT': '1', - }, silent=True) == 0 + extra_env=warn_changed_input, silent=True) == 0 assert conf.stderr_contains('expected_stderr') assert conf._run_conf('--savedefconfig=defconfig', dot_config='config', out_file='defconfig', - extra_env={ - 'KCONFIG_WARN_CHANGED_INPUT': '1', - }) == 0 + extra_env=warn_changed_input) == 0 assert conf.stderr_contains('expected_stderr') assert conf.config_matches('expected_defconfig') From 34f3b447600bcc56ae7c4d10db8dac754ad05357 Mon Sep 17 00:00:00 2001 From: Nicolas Schier Date: Tue, 29 Sep 2026 09:59:47 +0200 Subject: [PATCH 40/59] kconfig: tests: {old,save}defconfig: Forward dynamic keyword arguments Let kconfig test framework forward dynamic keyword arguments for 'olddefconfig' and 'savedefconfig' and update the _run_conf() calls in warn_changed_input to use the corrsponding wrappers instead. The warn_changed_input test needed to use _run_conf() as the {old,save}defconfig wrapper did not provide the necessary flexibility for common calls. With dynamic keyword arguments, there is no need to use the framework-internal _run_conf() any more. Reviewed-by: Julian Braha Signed-off-by: Nicolas Schier Link: https://patch.msgid.link/20260929-kconfig-tests-minor-updates-v2-4-c5902bee1d0c@fritz.com Signed-off-by: Nathan Chancellor --- scripts/kconfig/tests/conftest.py | 9 +++++---- scripts/kconfig/tests/warn_changed_input/__init__.py | 11 ++++------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/scripts/kconfig/tests/conftest.py b/scripts/kconfig/tests/conftest.py index 1f25de43128d..d22c080a4e40 100644 --- a/scripts/kconfig/tests/conftest.py +++ b/scripts/kconfig/tests/conftest.py @@ -159,13 +159,13 @@ class Conf: return self._run_conf('--oldconfig', dot_config=dot_config, interactive=True, in_keys=in_keys) - def olddefconfig(self, dot_config=None): + def olddefconfig(self, dot_config=None, **kw): """Run olddefconfig. dot_config: .config file to use for configuration base (optional) returncode: exit status of the Kconfig executable """ - return self._run_conf('--olddefconfig', dot_config=dot_config) + return self._run_conf('--olddefconfig', dot_config=dot_config, **kw) def defconfig(self, defconfig): """Run defconfig. @@ -229,14 +229,15 @@ class Conf: return self._allconfig('rand', all_config, extra_env=extra_env) - def savedefconfig(self, dot_config): + def savedefconfig(self, dot_config, out_file='defconfig', **kw): """Run savedefconfig. dot_config: .config file for input + out_file: defconfig file for output returncode: exit status of the Kconfig executable """ return self._run_conf('--savedefconfig={}'.format(out_file), - dot_config=dot_config, out_file='defconfig') + dot_config=dot_config, out_file=out_file, **kw) def listnewconfig(self, dot_config=None): """Run listnewconfig. diff --git a/scripts/kconfig/tests/warn_changed_input/__init__.py b/scripts/kconfig/tests/warn_changed_input/__init__.py index a5f07d4a882c..17b602dd280e 100644 --- a/scripts/kconfig/tests/warn_changed_input/__init__.py +++ b/scripts/kconfig/tests/warn_changed_input/__init__.py @@ -15,17 +15,14 @@ def test(conf): assert conf.olddefconfig('config') == 0 assert 'user-provided values changed by Kconfig' not in conf.stderr - assert conf._run_conf('--olddefconfig', dot_config='config', - extra_env=warn_changed_input) == 0 + assert conf.olddefconfig('config', extra_env=warn_changed_input) == 0 assert conf.stderr_contains('expected_stderr') assert conf.config_matches('expected_config') - assert conf._run_conf('--olddefconfig', dot_config='config', - extra_env=warn_changed_input, silent=True) == 0 + assert conf.olddefconfig('config', extra_env=warn_changed_input, + silent=True) == 0 assert conf.stderr_contains('expected_stderr') - assert conf._run_conf('--savedefconfig=defconfig', dot_config='config', - out_file='defconfig', - extra_env=warn_changed_input) == 0 + assert conf.savedefconfig('config', extra_env=warn_changed_input) == 0 assert conf.stderr_contains('expected_stderr') assert conf.config_matches('expected_defconfig') From 69cb84e259b163671ff241621544368af84c7c8f Mon Sep 17 00:00:00 2001 From: Guillaume Tucker Date: Mon, 7 Sep 2026 13:10:04 +0200 Subject: [PATCH 41/59] scripts: add TOML config to container tool Add support for a TOML configuration file to the scripts/container tool. This improves user experience by not having to keep passing the same command line options all the time or overly relying on built-in default values. Include the concept of 'profiles' with different named sections in the file to cover various use cases. Command line options take precedence over the config file, and values defined in profile sections take precedence over the default one. Add a -c option to override the location of the .container.toml config file which should otherwise be located in the current working directory. If not found, the file is silently ignored as it is not strictly required unless the -c option is used. Add a -p option to choose a particular profile section in the config file rather than the default. Signed-off-by: Guillaume Tucker Acked-by: Nicolas Schier Link: https://patch.msgid.link/68b14a76a4cb2e6437c869bab948983a98acffbd.1788779245.git.gtucker@gtucker.io Signed-off-by: Nathan Chancellor --- scripts/container | 86 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 74 insertions(+), 12 deletions(-) diff --git a/scripts/container b/scripts/container index b05333d8530b..a3a55180d6ed 100755 --- a/scripts/container +++ b/scripts/container @@ -1,17 +1,19 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0-only -# Copyright (C) 2025 Guillaume Tucker +# Copyright (C) 2025-2026 Guillaume Tucker """Containerized builds""" import abc import argparse +import dataclasses import logging import os import pathlib import shutil import subprocess import sys +import tomllib import uuid @@ -20,10 +22,17 @@ class ContainerRuntime(abc.ABC): name = None # Property defined in each implementation class - def __init__(self, args, logger): - self._uid = args.uid or os.getuid() - self._gid = args.gid or args.uid or os.getgid() - self._env_file = args.env_file + def __init__(self, args, config, logger): + def _first_not_none(*args): + return next((item for item in args if item is not None)) + + self._uid = _first_not_none( + args.uid, config.uid, os.getuid() + ) + self._gid = _first_not_none( + args.gid, config.gid, args.uid, config.uid, os.getgid() + ) + self._env_file = args.env_file or config.env_file self._shell = args.shell self._logger = logger @@ -131,6 +140,42 @@ class Runtimes: raise ValueError("no runtime found") +@dataclasses.dataclass +class Config: + """Container configuration""" + image: str = None + runtime: str = None + env_file: str = None + uid: int = None + gid: int = None + + @classmethod + def from_toml(cls, config_file_path, profile_name): + """Create a config object from a TOML file""" + if not config_file_path: + config_file_path = '.container.toml' + if not os.path.exists(config_file_path): + return cls() + elif not os.path.exists(config_file_path): + raise ValueError(f"config file not found: {config_file_path}") + with open(config_file_path, 'rb') as config_file: + config = tomllib.load(config_file) + default = config.get('DEFAULT', {}) + if not profile_name: + profile = {} + else: + profile = config.get(profile_name) + if profile is None: + raise ValueError(f"unknown profile: {profile_name}") + kwargs = { + name: type(value) for (name, type, value) in ( + (op.name, op.type, profile.get(op.name, default.get(op.name))) + for op in dataclasses.fields(cls) + ) if value is not None + } + return cls(**kwargs) + + def _get_logger(verbose): """Set up a logger with the appropriate level""" logger = logging.getLogger('container') @@ -147,13 +192,19 @@ def main(args): """Main entry point for the container tool""" logger = _get_logger(args.verbose) try: - cls = Runtimes.get(args.runtime) if args.runtime else Runtimes.find() + config = Config.from_toml(args.config_file, args.config_profile) + runtime = args.runtime or config.runtime + cls = Runtimes.get(runtime) if runtime else Runtimes.find() except ValueError as ex: logger.error(ex) return 1 logger.debug("runtime: %s", cls.name) - logger.debug("image: %s", args.image) - return cls(args, logger).run(args.image, args.cmd) + image = args.image or config.image + if not image: + logger.error("no image specified") + return 1 + logger.debug("image: %s", image) + return cls(args, config, logger).run(image, args.cmd) if __name__ == '__main__': @@ -162,18 +213,28 @@ if __name__ == '__main__': description="See the documentation for more details: " "https://docs.kernel.org/dev-tools/container.html" ) + parser.add_argument( + '-c', '--config-file', + help="Path to the config file. If not specified, the default is to " + "look for .container.toml in the current working directory." + ) parser.add_argument( '-e', '--env-file', help="Path to an environment file to load in the container." ) parser.add_argument( - '-g', '--gid', + '-g', '--gid', type=int, help="Group ID to use inside the container." ) parser.add_argument( - '-i', '--image', required=True, + '-i', '--image', help="Container image name." ) + parser.add_argument( + '-p', '--config-profile', + help="Profile section to use in the config file. This will override " + "any values defined in the DEFAULT section." + ) parser.add_argument( '-r', '--runtime', choices=Runtimes.get_names(), help="Container runtime name. If not specified, the first one found " @@ -184,9 +245,10 @@ if __name__ == '__main__': help="Run the container in an interactive shell." ) parser.add_argument( - '-u', '--uid', + '-u', '--uid', type=int, help="User ID to use inside the container. If the -g option is not " - "specified, the user ID will also be set as the group ID." + "specified and no group ID is defined in the configuration file, the " + "user ID will also be set as the group ID." ) parser.add_argument( '-v', '--verbose', action='store_true', From 56a124338eab93aaa5f3f416ff25f5bf5558fc78 Mon Sep 17 00:00:00 2001 From: Guillaume Tucker Date: Mon, 7 Sep 2026 13:10:05 +0200 Subject: [PATCH 42/59] Documentation: dev-tools: update container.rst with config file Update the container.rst documentation page with a new section for the TOML configuration file, its associated command line options and an intro paragraph. Also clarify how UIDs and GIDs are used as a GID can be set in the config file while a UID is provided on the command line. Provide a sample config using the TuxMake images with an example to show how to simplify the command line syntax. Bump the requirement from Python 3.10 to 3.11 which is when tomllib was introduced since it is a new dependency. Signed-off-by: Guillaume Tucker Link: https://patch.msgid.link/fa2dccdbea6a444ecf25f705ec96c5ad7ebd7497.1788779245.git.gtucker@gtucker.io Signed-off-by: Nathan Chancellor --- Documentation/dev-tools/container.rst | 94 ++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 3 deletions(-) diff --git a/Documentation/dev-tools/container.rst b/Documentation/dev-tools/container.rst index 9e23f79d5ae1..5c2957da1ec7 100644 --- a/Documentation/dev-tools/container.rst +++ b/Documentation/dev-tools/container.rst @@ -26,6 +26,11 @@ Command line syntax:: Available options: +``-c, --config-file CONFIG_FILE`` + + Path to the config file. If not specified, the default is to look for + ``.container.toml`` in the current working directory. + ``-e, --env-file ENV_FILE`` Path to an environment file to load in the container. @@ -38,6 +43,11 @@ Available options: Container image name (required). +``-p, --config-profile CONFIG_PROFILE`` + + Profile section to use in the config file. This will override any values + defined in the ``DEFAULT`` section. + ``-r, --runtime RUNTIME`` Container runtime name. Supported runtimes: ``podman``, ``docker``. @@ -53,8 +63,8 @@ Available options: User id to use inside the container. - If the ``-g`` option is not specified, the user id will also be used for - the group id. + If the ``-g`` option is not specified and no group id is defined in the + configuration file, the user id will also be set as the group id. ``-v, --verbose`` @@ -86,9 +96,13 @@ container with SIGINT (Ctrl-C). To run commands interactively with a TTY, the shell directly rather than the parent ``container`` process. To exit an interactive shell, use Ctrl-D or ``exit``. +A :ref:`configuration file` may be used to facilitate running +containers for various use cases. It also removes the burden of repeatedly +providing the same options on the command line. + .. note:: - The only host requirement aside from a container runtime is Python 3.10 or + The only host requirement aside from a container runtime is Python 3.11 or later. .. note:: @@ -151,6 +165,11 @@ with user id 1234 which can access the files in the volume but not in the user the kernel tree but it is worth highlighting here as it might matter for special corner cases. +Group IDs (GID) follow the same logic as user IDs (UID). When specifying a UID +via the ``--uid`` option or in the configuration file, the GID takes the same +value as the UID by default. A specific GID can be set via ``--gid`` or the +configuration file. + .. note:: Podman's `Docker compatibility @@ -225,3 +244,72 @@ To build the HTML documentation, which requires the ``kdocs`` image built with ``make PREFIX=kernel.org/ extra`` as it's not a compiler toolchain:: scripts/container -i kernel.org/kdocs make htmldocs + +.. _config_file: + +Configuration File +================== + +By default, the tool will look for a configuration file named +``.container.toml`` in the current working directory. If not found, it will be +silently ignored as it's not required. Alternatively, any other path can be +specified with the ``-c`` option in which case the file needs to be present or +an error will be raised. + +Its data follows the standard TOML format and is made up of different sections +with ``DEFAULT`` as the default one. Other sections may be used to define +alternative profiles under arbitrary names which can be selected by the ``-p`` +option. Command line options take precedence over configuration values, and +profile sections take precedence over the default one. + +Supported options in each section are: + +``env_file`` + + Path to an environment file to load in the container, equivalent to the + ``-e`` command line option. + +``image`` + + Name of the container image to use, equivalent to the ``-i`` command line + option. + +``runtime`` + + Name of the container runtime, equivalent to the ``-r`` command line option. + +``uid`` / ``gid`` (integers) + + User and group id numbers to use inside the container, equivalents to the + ``-u`` and ``-g`` command line options respectively. + + +Here's a sample configuration with an extra ``clang`` profile:: + + [DEFAULT] + runtime = "podman" + image = "tuxmake/korg-gcc" + + [clang] + image = "tuxmake/korg-clang" + env_file = ".clang.env" + +It mentions a ``.clang.env`` file which simply contains this flag:: + + LLVM=1 + +Let's take a look again at this example mentioned earlier:: + + scripts/container -i docker.io/tuxmake/korg-clang -- make LLVM=1 defconfig + +Using the configuration file, it can now be simplified into this:: + + scripts/container -p clang -- make defconfig + +Then to override the default user, the ``-u`` option can still be used:: + + scripts/container -p clang -u0 -- make defconfig + +This also illustrates how values take precedence over each other: the runtime +is loaded from the ``DEFAULT`` section, the image from the ``clang`` section +and the user ID from the command line ``-u`` option. From fd1ef9b669e969cd57261a7f0e94823f42e10cda Mon Sep 17 00:00:00 2001 From: Guillaume Tucker Date: Mon, 7 Sep 2026 13:10:06 +0200 Subject: [PATCH 43/59] Documentation: dev-tools: refer to user ID rather than id User and group identifiers are commonly known as IDs i.e. UID and GID written in capital letters rather than lowercase 'id'. Update the scripts/container documentation accordingly. Signed-off-by: Guillaume Tucker Link: https://patch.msgid.link/5585f8bc5c64e005aa8e4b7651f7cbcac0d93ad3.1788779245.git.gtucker@gtucker.io Signed-off-by: Nathan Chancellor --- Documentation/dev-tools/container.rst | 30 +++++++++++++-------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Documentation/dev-tools/container.rst b/Documentation/dev-tools/container.rst index 5c2957da1ec7..5dde95f6b222 100644 --- a/Documentation/dev-tools/container.rst +++ b/Documentation/dev-tools/container.rst @@ -11,7 +11,7 @@ various platforms, for example when a test bot has reported an issue which requires a specific version of a compiler or an external test suite. While this can already be done by users who are familiar with containers, having a dedicated tool in the kernel tree lowers the barrier to entry by solving common -problems once and for all (e.g. user id management). It also makes it easier +problems once and for all (e.g. user ID management). It also makes it easier to share an exact command line leading to a particular result. The main use case is likely to be kernel builds but virtually anything can be run: KUnit, checkpatch etc. provided a suitable image is available. @@ -37,7 +37,7 @@ Available options: ``-g, --gid GID`` - Group id to use inside the container. + Group ID to use inside the container. ``-i, --image IMAGE`` @@ -61,10 +61,10 @@ Available options: ``-u, --uid UID`` - User id to use inside the container. + User ID to use inside the container. - If the ``-g`` option is not specified and no group id is defined in the - configuration file, the user id will also be set as the group id. + If the ``-g`` option is not specified and no group ID is defined in the + configuration file, the user ID will also be set as the group ID. ``-v, --verbose`` @@ -81,7 +81,7 @@ Usage It's entirely up to the user to choose which image to use and the ``CMD`` arguments are passed directly as an arbitrary command line to run in the container. The tool will take care of mounting the source tree as the current -working directory and adjust the user and group id as needed. +working directory and adjust the user and group ID as needed. The container image which would typically include a compiler toolchain is provided by the user and selected via the ``-i`` option. The container runtime @@ -145,22 +145,22 @@ User IDs This is an area where the behaviour will vary slightly depending on the container runtime. The goal is to run commands as the user invoking the tool. -With Podman, a namespace is created to map the current user id to a different +With Podman, a namespace is created to map the current user ID to a different one in the container (1000 by default). With Docker, while this is also possible with recent versions it requires a special feature to be enabled in the daemon so it's not used here for simplicity. Instead, the container is run -with the current user id directly. In both cases, this will provide the same +with the current user ID directly. In both cases, this will provide the same file permissions for the kernel source tree mounted as a volume. The only -difference is that when using Docker without a namespace, the user id may not +difference is that when using Docker without a namespace, the user ID may not be the same as the default one set in the image. -Say, we're using an image which sets up a default user with id 1000 and the -current user calling the ``container`` tool has id 1234. The kernel source +Say, we're using an image which sets up a default user with ID 1000 and the +current user calling the ``container`` tool has ID 1234. The kernel source tree was checked out by this same user so the files belong to user 1234. With -Podman, the container will be running as user id 1000 with a mapping to id 1234 -so that the files from the mounted volume appear to belong to id 1000 inside +Podman, the container will be running as user ID 1000 with a mapping to ID 1234 +so that the files from the mounted volume appear to belong to ID 1000 inside the container. With Docker and no namespace, the container will be running -with user id 1234 which can access the files in the volume but not in the user +with user ID 1234 which can access the files in the volume but not in the user 1000 home directory. This shouldn't be an issue when running commands only in the kernel tree but it is worth highlighting here as it might matter for special corner cases. @@ -280,7 +280,7 @@ Supported options in each section are: ``uid`` / ``gid`` (integers) - User and group id numbers to use inside the container, equivalents to the + User and group ID numbers to use inside the container, equivalents to the ``-u`` and ``-g`` command line options respectively. From 26d665bef44fa15f7551ee1efc19a1df331d6bbc Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Tue, 15 Sep 2026 13:43:31 +0300 Subject: [PATCH 44/59] kbuild: add header check facility as a manually run static analyzer There have been various attempts at adding a header test or check mechanism in the kernel build system. The header check primarily consists of ensuring headers are self-contained, have include guards, and, in some cases, pass kernel-doc. The main problems have been: - The dependency tracking creates undesirable artefacts (infamously also known as disgusting turds) in the build directory. - Gating the feature behind a kconfig option is complicated due to allyesconfig builds. It's possible, but requires a verbose and confusing negative proxy config option. - Naming the dependency tracking files with a dot prefix or placing them in a dot prefixed subdirectory in the build directory to hide them has been exceedingly difficult to achieve. (In part due to some Makefiles building files in subdirectory hierarchies.) - The debate which headers, if any, should really be self-contained is virtually open-ended. Approach the problem from a slightly different angle. Add a top level "headercheck" static analysis target which you have to explicitly run, and where you have to explicitly state which headers to check: $ make HEADER_CHECK="(headers|dirs)" headercheck For example: $ make HEADER_CHECK="include/drm drivers/gpu/drm/drm_draw_internal.h" headercheck You can specify multiple path/to/header.h or path/to/dir, relative to $(srctree), where path/to/dir is recursively scanned for any .h files. Add a generic %.header-check rule in scripts/Makefile.build to support this. Note that this rule should *not* be used in, say, driver or subsystem makefile targets, precisely because of the problems listed above. While this still generates the .header-check dependency tracking artefacts in the build directory, you will only get them as a result of manually running 'make headercheck', not as part of the regular build. This approach also allows anyone to check any headers in the tree, with no makefile modification or kconfig changes required. What you do with the results is up to you, and the header check facility does not enforce anything like it would as part of the regular build. Cc: Linus Torvalds Cc: Nathan Chancellor Cc: Nicolas Schier Cc: Jason Gunthorpe Cc: Masahiro Yamada Cc: linux-kbuild@vger.kernel.org Signed-off-by: Jani Nikula Tested-by: Randy Dunlap Acked-by: Randy Dunlap Link: https://patch.msgid.link/20260915104331.255636-1-jani.nikula@intel.com Signed-off-by: Nathan Chancellor --- .gitignore | 1 + Makefile | 25 ++++++++++++++++++++++++- scripts/Makefile.build | 21 +++++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9875120ea7bd..948c760b2704 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ *.gcno *.gcda *.gz +*.header-check *.i *.ko *.lex.c diff --git a/Makefile b/Makefile index c496d1639103..28502178746f 100644 --- a/Makefile +++ b/Makefile @@ -302,7 +302,7 @@ no-dot-config-targets := $(clean-targets) \ run-command no-sync-config-targets := $(no-dot-config-targets) %install modules_sign kernelrelease \ image_name -single-targets := %.a %.i %.ko %.lds %.ll %.lst %.mod %.o %.rsi %.s %/ +single-targets := %.a %.i %.ko %.lds %.ll %.lst %.mod %.o %.rsi %.s %.header-check %/ config-build := mixed-build := @@ -1544,6 +1544,26 @@ PHONY += scripts_gen_packed_field_checks scripts_gen_packed_field_checks: scripts_basic $(Q)$(MAKE) $(build)=scripts scripts/gen_packed_field_checks +# --------------------------------------------------------------------------- +# Header check + +# Check the headers in HEADER_CHECK=(headers|dirs) +PHONY += headercheck + +ifneq ($(HEADER_CHECK),) + +header-check-dirs := $(filter-out %.h,$(HEADER_CHECK)) +header-check-files := $(filter %.h,$(HEADER_CHECK)) $(if $(header-check-dirs),$(shell cd $(srctree) && find $(header-check-dirs) -name '*.h' 2>/dev/null)) +header-check-targets := $(patsubst %.h,%.header-check,$(sort $(header-check-files))) + +headercheck: + $(if $(header-check-targets),,$(error $@ found no headers in HEADER_CHECK="$(HEADER_CHECK)")) + $(Q)$(MAKE) $(header-check-targets) +else +headercheck: + $(error $@ requires HEADER_CHECK=(headers|dirs)) +endif + # --------------------------------------------------------------------------- # Install @@ -1890,6 +1910,8 @@ help: @echo ' versioncheck - Sanity check on version.h usage' @echo ' includecheck - Check for duplicate included header files' @echo ' headerdep - Detect inclusion cycles in headers' + @echo ' headercheck - Check headers in HEADER_CHECK=(headers|dirs) are' + @echo ' self-contained, have header guards, and pass kernel-doc.' @echo ' coccicheck - Check with Coccinelle' @echo ' kconfig-sym-check - Check for dangling Kconfig symbol references' @echo ' clang-analyzer - Check with clang static analyzer' @@ -2255,6 +2277,7 @@ clean: $(clean-dirs) -o -name '*.ll' \ -o -name '*.gcno' \ -o -name '*.long-type-*.txt' \ + -o -name '*.header-check' \ \) -type f -print \ -o -name '.tmp_*' -print \ | xargs rm -rf diff --git a/scripts/Makefile.build b/scripts/Makefile.build index 4349108e75e1..6127feb970c6 100644 --- a/scripts/Makefile.build +++ b/scripts/Makefile.build @@ -306,6 +306,27 @@ quiet_cmd_cc_lst_c = MKLST $@ $(obj)/%.lst: $(obj)/%.c FORCE $(call if_changed_dep,cc_lst_c) +# Compile C headers (.h) for header check +# --------------------------------------------------------------------------- + +# Check a header is self-contained, has header guards, and passes kernel-doc. +# +# This is for 'make HEADER_CHECK=(headers|dirs) headercheck'. Not to be confused +# with CONFIG_UAPI_HEADER_TEST. +# +# Please do *not* plug %.header-check into any configurable targets, unless you +# also figure out a way to completely hide the %.header-check artefacts. + +quiet_cmd_header_check = HDRCHK $(patsubst %.header-check,%.h,$@) + cmd_header_check = \ + $(CC) $(c_flags) -fsyntax-only -x c /dev/null -include $< -include $<; \ + PYTHONDONTWRITEBYTECODE=1 $(PYTHON3) $(KERNELDOC) -none $(KDOCFLAGS) \ + $(if $(findstring e, $(KBUILD_EXTRA_WARN))$(CONFIG_WERROR),-Werror) $<; \ + touch $@ + +$(obj)/%.header-check: $(obj)/%.h FORCE + $(call if_changed_dep,header_check) + # Compile Rust sources (.rs) # --------------------------------------------------------------------------- From fe366c131a13a5e126cee05f677afc8b731964d2 Mon Sep 17 00:00:00 2001 From: Brigham Campbell Date: Sat, 3 Oct 2026 22:01:13 -0600 Subject: [PATCH 45/59] .gitignore: ignore pacman package signatures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ignore package signatures which are generated by makepkg when package signing is enabled. Signed-off-by: Brigham Campbell Acked-by: Thomas Weißschuh Link: https://patch.msgid.link/20261003-ignore-pacman-signature-v1-1-d16a9a026f55@brighamcampbell.com Signed-off-by: Nathan Chancellor --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 948c760b2704..4bb3946f1b68 100644 --- a/.gitignore +++ b/.gitignore @@ -105,6 +105,7 @@ modules.order # # pacman files (make pacman-pkg) # +/*.pkg.*.sig /PKGBUILD /pacman/ From a7d32fa14bc74d1786a2f0513b6f0815bd4be311 Mon Sep 17 00:00:00 2001 From: Jan Stancek Date: Thu, 1 Oct 2026 09:11:10 +0200 Subject: [PATCH 46/59] .gitignore: drop Module.markers commit fc5377668c3d ("tracing: Remove markers") deleted the marker code and the modpost output. Follow-up commit 8723eaeff59a ("Kbuild: clean up marker") removed it from clean and mrproper targets. Both are from 2009, so the file is hopefully no longer in anyone's tree, drop it from .gitignore as well. Fixes: 8723eaeff59a ("Kbuild: clean up marker") Signed-off-by: Jan Stancek Reviewed-by: Nicolas Schier Link: https://patch.msgid.link/8cbe913b9f80dc6fea73a9e3d51429db2b38905f.1790838401.git.jstancek@redhat.com Signed-off-by: Nathan Chancellor --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 4bb3946f1b68..f0351b8a74d0 100644 --- a/.gitignore +++ b/.gitignore @@ -76,7 +76,6 @@ modules.order /vmlinux-gdb.py /vmlinuz /System.map -/Module.markers /modules.builtin /modules.builtin.modinfo /modules.builtin.ranges From c8dd1aa0274d8ad3df82ec1b064065f410ce99ed Mon Sep 17 00:00:00 2001 From: Leizhen Zhang Date: Mon, 5 Oct 2026 03:57:29 -0500 Subject: [PATCH 47/59] gen_init_cpio: stop parsing on lines with a missing argument list When a line in the cpio list consists of a file type followed only by whitespace and no newline (e.g. "slink " as the last line of the file), strtok(NULL, "\n") returns NULL. main() prints an error and sets ec, but then carries on and passes the NULL args pointer to the type handler, which hands it to sscanf() and crashes: AddressSanitizer: SEGV on unknown address 0x000000000000 #4 __isoc99_sscanf #5 cpio_mkslink_line usr/gen_init_cpio.c:169 Break out of the loop as is already done for the other format error, so that the error is reported and gen_init_cpio exits with a failure status. Found by fuzzing gen_init_cpio built with ASan/UBSan. Reproducer: printf 'slink ' > list && usr/gen_init_cpio list Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Assisted-by: Claude:claude-opus-5-5 ASan UBSan Signed-off-by: Leizhen Zhang Link: https://patch.msgid.link/20261005085729.872402-1-lzsx618@gmail.com Signed-off-by: Nathan Chancellor --- scripts/gen_init_cpio.c | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/gen_init_cpio.c b/scripts/gen_init_cpio.c index b7296edc6626..0f0c61ec21ca 100644 --- a/scripts/gen_init_cpio.c +++ b/scripts/gen_init_cpio.c @@ -756,6 +756,7 @@ int main (int argc, char *argv[]) "ERROR: incorrect format, newline required line %d: '%s'\n", line_nr, line); ec = -1; + break; } for (type_idx = 0; file_handler_table[type_idx].type; type_idx++) { From 72a61a7fef954152abd9df9def4f94816eb344ca Mon Sep 17 00:00:00 2001 From: Ting-Han Hou Date: Wed, 7 Oct 2026 03:44:13 -0500 Subject: [PATCH 48/59] checkkconfigsymbols.py: Parse Git status output line by line tree_is_dirty() iterates over the string returned by execute() one character at a time. Its status check therefore examines filenames as well as the two status columns. An otherwise clean tree containing an untracked README is reported as dirty because its name contains letters matched by [URMADC]. This prevents --commit and --diff from running, whereas an untracked notes.txt does not trigger the check. Split the output into lines before examining the two status columns so that filenames do not affect the dirty-tree check. Fixes: b1a3f243485f ("checkkconfigsymbols.py: make it Git aware") Assisted-by: LLM Signed-off-by: Ting-Han Hou Link: https://patch.msgid.link/CA+NtrhQqXgjT+4eg6W4P94Hc=DqdV3gQXz3EKKCxWvdWzfND6A@mail.gmail.com Signed-off-by: Nathan Chancellor --- scripts/checkkconfigsymbols.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/checkkconfigsymbols.py b/scripts/checkkconfigsymbols.py index 36c920e71313..9f7aed98d91c 100755 --- a/scripts/checkkconfigsymbols.py +++ b/scripts/checkkconfigsymbols.py @@ -245,7 +245,7 @@ def tree_is_dirty(): """Return true if the current working tree is dirty (i.e., if any file has been added, deleted, modified, renamed or copied but not committed).""" stdout = execute(["git", "status", "--porcelain"]) - for line in stdout: + for line in stdout.splitlines(): if re.findall(r"[URMADC]{1}", line[:2]): return True return False From c4c24fd63a8d56cfefd78678758f9be72e17c90a Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Wed, 7 Oct 2026 17:22:21 +0200 Subject: [PATCH 49/59] scripts/Kconfig.toolchain: Drop compiler symbols from dependencies Commit 454e00cf8ee5 ("kbuild: avoid re-running compiler and linker probes") moved many cc-option calls from the main Makefile into Kconfig. In the process, code such as ifdef CONFIG_CC_IS_GCC KBUILD_CFLAGS += $(call cc-option,-foo) endif was converted into Kconfig as config CC_OPT_FOO def_string "$(cc-option-str,-foo)" if CC_IS_GCC The 'if CC_IS_GCC' is generally unnecessary because it does not prevent the cc-option-str call from running (which is the primary reason the ifdef guards exists in the Makefile), as cc-option-str and the like are run before Kconfig even parses the dependency. It only impacts the symbol's visibility, which does not really matter in the general case, so remove these dependencies. Reviewed-by: Nicolas Schier Reviewed-by: Kees Cook Link: https://patch.msgid.link/20261007-scripts-kconfig-toolchain-improvements-v1-1-1e524178da11@kernel.org Signed-off-by: Nathan Chancellor --- scripts/Kconfig.toolchain | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/Kconfig.toolchain b/scripts/Kconfig.toolchain index d708a175fe48..c9a1692e6149 100644 --- a/scripts/Kconfig.toolchain +++ b/scripts/Kconfig.toolchain @@ -161,10 +161,10 @@ config CC_OPT_STRICT_FLEX_ARRAYS # gcc-10 renamed --param=allow-store-data-races=0 to -fno-allow-store-data-races config CC_OPT_ALLOW_STORE_DATA_RACES_PARAM - def_string "$(cc-option-str,--param=allow-store-data-races=0)" if CC_IS_GCC + def_string "$(cc-option-str,--param=allow-store-data-races=0)" config CC_OPT_NO_ALLOW_STORE_DATA_RACES - def_string "$(cc-option-str,-fno-allow-store-data-races)" if CC_IS_GCC + def_string "$(cc-option-str,-fno-allow-store-data-races)" config CC_HAS_MNOP_MCOUNT def_bool $(cc-option,-mnop-mcount) @@ -183,13 +183,13 @@ config CC_OPT_WNO_ADDRESS_OF_PACKED_MEMBER def_string "-Wno-address-of-packed-member" if $(cc-option,-Waddress-of-packed-member) config CC_OPT_WNO_FORMAT_OVERFLOW_NON_KPRINTF - def_string "-Wno-format-overflow-non-kprintf" if CC_IS_CLANG && $(cc-option,-Wformat-overflow-non-kprintf) + def_string "-Wno-format-overflow-non-kprintf" if $(cc-option,-Wformat-overflow-non-kprintf) config CC_OPT_WNO_FORMAT_TRUNCATION_NON_KPRINTF - def_string "-Wno-format-truncation-non-kprintf" if CC_IS_CLANG && $(cc-option,-Wformat-truncation-non-kprintf) + def_string "-Wno-format-truncation-non-kprintf" if $(cc-option,-Wformat-truncation-non-kprintf) config CC_OPT_WNO_DEFAULT_CONST_INIT_UNSAFE - def_string "-Wno-default-const-init-unsafe" if CC_IS_CLANG && $(cc-option,-Wdefault-const-init-unsafe) + def_string "-Wno-default-const-init-unsafe" if $(cc-option,-Wdefault-const-init-unsafe) config CC_OPT_WNO_DANGLING_POINTER def_string "-Wno-dangling-pointer" if $(cc-option,-Wdangling-pointer) From 7202c08793a243fb4c7f9a1b170e052b2f038b33 Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Wed, 7 Oct 2026 17:22:22 +0200 Subject: [PATCH 50/59] kconfig: Improve ergonomics around disabling warnings with cc-option This is essentially a port of commit 550ccb178de2 ("Make 'cc-option' work correctly for the -Wno-xyzzy pattern") to scripts/Kconfig.include, which simplifies the definition of many of the newly added CC_OPT_WNO_ symbols in scripts/Kconfig.toolchain and matches cc-option's behavior in Kbuild. Reviewed-by: Nicolas Schier Reviewed-by: Kees Cook Link: https://patch.msgid.link/20261007-scripts-kconfig-toolchain-improvements-v1-2-1e524178da11@kernel.org Signed-off-by: Nathan Chancellor --- scripts/Kconfig.include | 2 +- scripts/Kconfig.toolchain | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/scripts/Kconfig.include b/scripts/Kconfig.include index 622d21e6953f..a7d9201acfcc 100644 --- a/scripts/Kconfig.include +++ b/scripts/Kconfig.include @@ -26,7 +26,7 @@ failure = $(if-success,$(1),n,y) # $(cc-probe,) # The command $(cc-option) and $(cc-option-str) test with. Not meant # to be called directly; use one of those two instead. -cc-probe = trap "rm -rf .tmp_$$" EXIT; mkdir .tmp_$$; $(CC) -Werror $(CLANG_FLAGS) $(1) -c -x c /dev/null -o .tmp_$$/tmp.o +cc-probe = trap "rm -rf .tmp_$$" EXIT; mkdir .tmp_$$; $(CC) -Werror $(CLANG_FLAGS) $(shell,echo $(1) | sed "s;^-Wno-;-W;") -c -x c /dev/null -o .tmp_$$/tmp.o # $(cc-option,) # Return y if the compiler supports , n otherwise diff --git a/scripts/Kconfig.toolchain b/scripts/Kconfig.toolchain index c9a1692e6149..254393d4f98a 100644 --- a/scripts/Kconfig.toolchain +++ b/scripts/Kconfig.toolchain @@ -180,19 +180,19 @@ config LD_OPT_NO_WARN_RWX_SEGMENTS def_string "$(ld-option-str,--no-warn-rwx-segments)" config CC_OPT_WNO_ADDRESS_OF_PACKED_MEMBER - def_string "-Wno-address-of-packed-member" if $(cc-option,-Waddress-of-packed-member) + def_string "$(cc-option-str,-Wno-address-of-packed-member)" config CC_OPT_WNO_FORMAT_OVERFLOW_NON_KPRINTF - def_string "-Wno-format-overflow-non-kprintf" if $(cc-option,-Wformat-overflow-non-kprintf) + def_string "$(cc-option-str,-Wno-format-overflow-non-kprintf)" config CC_OPT_WNO_FORMAT_TRUNCATION_NON_KPRINTF - def_string "-Wno-format-truncation-non-kprintf" if $(cc-option,-Wformat-truncation-non-kprintf) + def_string "$(cc-option-str,-Wno-format-truncation-non-kprintf)" config CC_OPT_WNO_DEFAULT_CONST_INIT_UNSAFE - def_string "-Wno-default-const-init-unsafe" if $(cc-option,-Wdefault-const-init-unsafe) + def_string "$(cc-option-str,-Wno-default-const-init-unsafe)" config CC_OPT_WNO_DANGLING_POINTER - def_string "-Wno-dangling-pointer" if $(cc-option,-Wdangling-pointer) + def_string "$(cc-option-str,-Wno-dangling-pointer)" config CC_OPT_WVLA_LARGER_THAN def_string "$(cc-option-str,-Wvla-larger-than=1)" @@ -201,7 +201,7 @@ config CC_HAS_WSTRINGOP_OVERFLOW def_bool $(cc-option,-Wstringop-overflow) config CC_OPT_WNO_UNTERMINATED_STRING_INITIALIZATION - def_string "-Wno-unterminated-string-initialization" if $(cc-option,-Wunterminated-string-initialization) + def_string "$(cc-option-str,-Wno-unterminated-string-initialization)" config CC_OPT_WERROR_DESIGNATED_INIT def_string "$(cc-option-str,-Werror=designated-init)" @@ -210,13 +210,13 @@ config CC_OPT_WENUM_CONVERSION def_string "$(cc-option-str,-Wenum-conversion)" config CC_OPT_WNO_PACKED_NOT_ALIGNED - def_string "-Wno-packed-not-aligned" if $(cc-option,-Wpacked-not-aligned) + def_string "$(cc-option-str,-Wno-packed-not-aligned)" config CC_OPT_WNO_FORMAT_OVERFLOW - def_string "-Wno-format-overflow" if $(cc-option,-Wformat-overflow) + def_string "$(cc-option-str,-Wno-format-overflow)" config CC_OPT_WNO_STRINGOP_TRUNCATION - def_string "-Wno-stringop-truncation" if $(cc-option,-Wstringop-truncation) + def_string "$(cc-option-str,-Wno-stringop-truncation)" config CC_HAS_COUNTED_BY bool From 2160297cabd4bd121ef2e7327c6a6f376c0fbf3c Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Fri, 9 Oct 2026 10:46:17 +0100 Subject: [PATCH 51/59] kconfig: cleanup stale comment about 'keys' array 'keys' was removed from 'dialog_textbox' in: commit e14f1242a8be ("kconfig: menuconfig: simplify global jump key assignment") but the comment referencing it was never removed. Let's cleanup this stale comment. Signed-off-by: Julian Braha Reviewed-by: Nicolas Schier Link: https://patch.msgid.link/20261009094617.17322-1-julianbraha@gmail.com Signed-off-by: Nathan Chancellor --- scripts/kconfig/lxdialog/textbox.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/kconfig/lxdialog/textbox.c b/scripts/kconfig/lxdialog/textbox.c index 0abaf635978f..3fe7682fc8a8 100644 --- a/scripts/kconfig/lxdialog/textbox.c +++ b/scripts/kconfig/lxdialog/textbox.c @@ -145,8 +145,6 @@ static void refresh_text_box(WINDOW *dialog, WINDOW *box, int boxh, int boxw, /* * Display text from a file in a dialog box. - * - * keys is a null-terminated array */ int dialog_textbox(const char *title, const char *tbuf, int initial_height, int initial_width, int *_vscroll, int *_hscroll, From 52f26261e27dbdb7c6dea75d8f1a92ea805b2c58 Mon Sep 17 00:00:00 2001 From: Leizhen Zhang Date: Thu, 8 Oct 2026 11:45:21 -0500 Subject: [PATCH 52/59] kconfig: fix infinite loop in conf_choice() when no choice value is visible If a choice is visible but none of its choice values is, conf_choice() counts cnt == 0 visible values and finds no default. When the choice has to be asked, e.g. because a new symbol was added to the same menu (oldconfig, syncconfig) or because stdin is at EOF (oldaskconfig), the loop that maps the answer to a menu entry never finds one and the while (1) loop repeats forever, printing "choice[1-0?]: 0" each time. This happens in the tree for s390 when the compiler supports none of the -march=z* options (e.g. a non-s390 host compiler), so that all values of the "Processor type" choice are invisible: $ make ARCH=s390 defconfig $ sed -i '/^CONFIG_NR_CPUS=/d' .config $ make ARCH=s390 oldconfig # hangs Before the removal of tristate choice support, conf_choice() returned early in this case. Do the same when there is no visible choice value. Found by fuzzing Kconfig input with ASan/UBSan. Fixes: fde192511bdb ("kconfig: remove tristate choice support") Assisted-by: Claude:claude-opus-5-5 libFuzzer Signed-off-by: Leizhen Zhang Reviewed-by: Julian Braha Tested-by: Julian Braha Link: https://patch.msgid.link/20261008164521.3684735-1-lzsx618@gmail.com Signed-off-by: Nathan Chancellor --- scripts/kconfig/conf.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/kconfig/conf.c b/scripts/kconfig/conf.c index ca6d9d735922..31c6fd25e6ec 100644 --- a/scripts/kconfig/conf.c +++ b/scripts/kconfig/conf.c @@ -483,6 +483,9 @@ static void conf_choice(struct menu *menu) } printf("\n"); } + /* Nothing to ask if none of the choice values is visible. */ + if (!cnt) + return; printf("%*schoice", indent - 1, ""); if (cnt == 1) { printf("[1]: 1\n"); From a8f72a75b38a153d32a59b2ebca1d7079db1dd1a Mon Sep 17 00:00:00 2001 From: Leizhen Zhang Date: Thu, 8 Oct 2026 11:45:40 -0500 Subject: [PATCH 53/59] kconfig: use the value argument in sym_get_near_range_bound() sym_get_near_range_bound() is meant to check whether the given value is outside of the active range of an int/hex symbol, and is called with the symbol's current value from sym_calc_value() and with the default value from sym_get_string_default(). Since commit 9b8719973ee2 ("kconfig: use unsigned integers for hex range checks") it ignores its value argument and uses the current value of the symbol (via sym_get_range_val()) instead. As a result, an out-of-range default is no longer clamped in sym_get_string_default(), and savedefconfig writes out values that are equal to the effective default again, for example CONFIG_A=4 for: config A int "a" range 1 4 default 8 The existing savedefconfig_range test did not catch this because it only covers symbols without a prompt. Parse the value argument again, and add a test case for a symbol with a prompt. Found while fuzzing Kconfig input with ASan/UBSan. Fixes: 9b8719973ee2 ("kconfig: use unsigned integers for hex range checks") Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Leizhen Zhang Tested-by: Julian Braha Reviewed-by: Julian Braha Link: https://patch.msgid.link/20261008164540.3686383-1-lzsx618@gmail.com Signed-off-by: Nathan Chancellor --- scripts/kconfig/symbol.c | 5 ++++- scripts/kconfig/tests/savedefconfig_range/Kconfig | 7 +++++++ scripts/kconfig/tests/savedefconfig_range/config | 1 + 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/scripts/kconfig/symbol.c b/scripts/kconfig/symbol.c index dcb4b45e674b..4ced4dd6485b 100644 --- a/scripts/kconfig/symbol.c +++ b/scripts/kconfig/symbol.c @@ -195,7 +195,10 @@ static struct symbol *sym_get_near_range_bound(struct symbol *sym, prop = sym_get_range_prop(sym); if (!prop) return NULL; - val = sym_get_range_val(sym, base); + if (base == 10) + val.s = strtoll(value, NULL, base); + else + val.u = strtoull(value, NULL, base); range_sym = prop->expr->left.sym; val2 = sym_get_range_val(range_sym, base); diff --git a/scripts/kconfig/tests/savedefconfig_range/Kconfig b/scripts/kconfig/tests/savedefconfig_range/Kconfig index fd59d9082ed5..d879870a5267 100644 --- a/scripts/kconfig/tests/savedefconfig_range/Kconfig +++ b/scripts/kconfig/tests/savedefconfig_range/Kconfig @@ -58,3 +58,10 @@ config INT_CONDITIONAL_DEFAULT int range 1 2 default 3 if USE_DEFAULT + +# Default outside of the range of a symbol with a prompt + +config INT_PROMPT_DEFAULT_ABOVE_RANGE + int "int with prompt" + range 1 4 + default 8 diff --git a/scripts/kconfig/tests/savedefconfig_range/config b/scripts/kconfig/tests/savedefconfig_range/config index d939cfe5fd34..079a3107a02b 100644 --- a/scripts/kconfig/tests/savedefconfig_range/config +++ b/scripts/kconfig/tests/savedefconfig_range/config @@ -5,3 +5,4 @@ CONFIG_HEX_DEFAULT_ABOVE_RANGE=0x20 CONFIG_INT_IMPLICIT_DEFAULT_ZERO=1 CONFIG_INT_CONDITIONAL_RANGE=2 CONFIG_INT_CONDITIONAL_DEFAULT=2 +CONFIG_INT_PROMPT_DEFAULT_ABOVE_RANGE=4 From d26ab84593e63bc5395ed9b518502830fda3367c Mon Sep 17 00:00:00 2001 From: Ting-Han Hou Date: Sat, 10 Oct 2026 01:58:32 +0800 Subject: [PATCH 54/59] clang-tools: Import os for broken pipe handling The BrokenPipeError handler in run-clang-tools.py calls os.open() and os.dup2() without importing os. If this handler is reached, it raises NameError instead of redirecting stdout and exiting with status 1. Import os so the handler can work as intended. Fixes: 87c7ee67deb7 ("scripts: handle BrokenPipeError for python scripts") Assisted-by: LLM Signed-off-by: Ting-Han Hou Link: https://patch.msgid.link/20261009175836.2277-1-ue081723@gmail.com Signed-off-by: Nathan Chancellor --- scripts/clang-tools/run-clang-tools.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/clang-tools/run-clang-tools.py b/scripts/clang-tools/run-clang-tools.py index e78be82aa693..79d58b34e7ac 100755 --- a/scripts/clang-tools/run-clang-tools.py +++ b/scripts/clang-tools/run-clang-tools.py @@ -12,6 +12,7 @@ compile_commands.json. import argparse import json import multiprocessing +import os import subprocess import sys From 3bf2237f59af842e546fa4b2c146198db95d1519 Mon Sep 17 00:00:00 2001 From: Ting-Han Hou Date: Tue, 6 Oct 2026 08:19:54 -0700 Subject: [PATCH 55/59] scripts/make_fit: Ignore schema checks when decomposing DTBs With CHECK_DTBS enabled, Kbuild appends a schema validation command to the fdtoverlay command saved in a composite DTB's .cmd file. When DTB decomposition is requested, process_dtb() treats every word after -i as an input file, including the semicolon and the validator arguments. FIT generation then fails with FileNotFoundError while opening or checking the size of those words as files. Limit the input list to the fdtoverlay command before extracting its arguments. This preserves the base DTB and overlay order and ignores the subsequent schema validation command. Fixes: 17c31aded9a1 ("scripts/make_fit: Support decomposing DTBs") Assisted-by: LLM Signed-off-by: Ting-Han Hou Reviewed-by: Simon Glass Link: https://patch.msgid.link/CA+NtrhQBK8uW32qO3dXxVxO1GSN-0EJfyJ16tnyx0o52T17WtQ@mail.gmail.com Signed-off-by: Nathan Chancellor --- scripts/make_fit.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/make_fit.py b/scripts/make_fit.py index 15ba26974fd7..9bd35aa72119 100755 --- a/scripts/make_fit.py +++ b/scripts/make_fit.py @@ -292,7 +292,8 @@ def process_dtb(fname, args): if 'scripts/dtc/fdtoverlay' in cmd: # This depends on the structure of the composite DTB command - files = cmd.split() + # Ignore any subsequent commands, such as schema validation + files = cmd.split(';', 1)[0].split() files = files[files.index('-i') + 1:] else: files = [fname] From 46d4ccf3b621368af5e98187e9e21dccd8beb5cc Mon Sep 17 00:00:00 2001 From: Aaron Tomlin Date: Fri, 25 Sep 2026 14:12:30 -0400 Subject: [PATCH 56/59] localmodconfig: Explicitly report boolean configs during integrity check When running make localmodconfig, streamline_config.pl reads loaded modules and maps each module object to its corresponding Kconfig symbol(s) using repository Makefiles. In several instances, a loaded module corresponds to an object controlled by a boolean symbol (e.g. config FOO declared as bool), or is a composite module that depends on a boolean configuration. Because boolean options only accept "=y" or disabled, they cannot be configured as loadable modules ("=m"). Previously, if such a symbol was not already built-in ("=y") in the baseline configuration, the final integrity check emitted an ambiguous diagnostic. For example foo did not have configs CONFIG_FOO Further investigation is required by developers to determine whether the module was out-of-tree, its dependencies were unmet, or it failed due to a type constraint. Address this by: 1. Tracking symbol types in a %types hash during Kconfig parsing. 2. Enhancing the final integrity check when running localmodconfig (!$localyesconfig) to explicitly annotate boolean symbols. For example: module cec did not have configs CONFIG_CEC_CORE (tristate) CONFIG_RAS_CEC (boolean, not supported with =m) Signed-off-by: Aaron Tomlin Acked-by: Steven Rostedt Link: https://patch.msgid.link/20260925181230.130525-1-atomlin@atomlin.com Signed-off-by: Nathan Chancellor --- scripts/kconfig/streamline_config.pl | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/scripts/kconfig/streamline_config.pl b/scripts/kconfig/streamline_config.pl index 8677d1ca06a7..6a52a98eb40e 100755 --- a/scripts/kconfig/streamline_config.pl +++ b/scripts/kconfig/streamline_config.pl @@ -145,6 +145,7 @@ my %prompts; my %objects; my %config2kfile; my %defaults; +my %types; my $var; my $iflevel = 0; my @ifdeps; @@ -224,6 +225,7 @@ sub read_kconfig { } elsif ($state ne "NONE" && /^\s*def(_(bool|tristate)|ault)\s+(\S.*)$/) { my $dep = $3; $defaults{$config} = 1; + $types{$config} = $2 if (defined($2)); if ($dep !~ /^\s*(y|m|n)\s*$/) { $dep =~ s/.*\sif\s+//; $depends{$config} .= " " . $dep; @@ -240,7 +242,13 @@ sub read_kconfig { } # configs without prompts must be selected - } elsif ($state ne "NONE" && /^\s*(tristate\s+\S|prompt\b)/) { + } elsif ($state ne "NONE" && /^\s*(bool|boolean)(\s+\S.*)?$/) { + $types{$config} = "bool"; + $prompts{$config} = 1 if (defined($2)); + } elsif ($state ne "NONE" && /^\s*tristate(\s+\S.*)?$/) { + $types{$config} = "tristate"; + $prompts{$config} = 1 if (defined($1)); + } elsif ($state ne "NONE" && /^\s*prompt\b/) { # note if the config has a prompt $prompts{$config} = 1; @@ -707,7 +715,15 @@ foreach my $module (keys(%modules)) { } print STDERR "module $module did not have configs"; foreach my $conf (@arr) { - print STDERR " " , $conf; + my $c = $conf; + $c =~ s/^CONFIG_//; + if (!$localyesconfig && defined($types{$c}) && $types{$c} eq "bool") { + print STDERR " $conf (boolean, not supported with =m)"; + } elsif (defined($types{$c})) { + print STDERR " $conf ($types{$c})"; + } else { + print STDERR " " , $conf; + } } print STDERR "\n"; } From 0d4d70da348b066b02f5aa4b893b13c4e32dc838 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Sat, 10 Oct 2026 01:19:10 -0700 Subject: [PATCH 57/59] kbuild: Mark the gzip recipes as recursive Since commit 48ebe1cbbeb51 ("kbuild: compress the kernel with pigz if available"), cmd_gzip runs pigz through scripts/jobserver-exec, which takes the job slots make has free. GNU Make passes the jobserver pipe only to recipes it treats as recursive, but leaves --jobserver-auth in MAKEFLAGS for all of them. The make 4.3 in CentOS Stream 9 also hides the pipe in sub-makes (a backport of the fix for GNU Make bug 58232), so jobserver-exec cannot open it, runs pigz on one thread, and prints: WARNING: Unable to reopen jobserver read-side pipe: FileNotFoundError(2, 'No such file or directory') Mark every recipe that can run cmd_gzip as recursive with "+", as commit ecab4115c44cc ("kbuild: mark `rustc` (and others) invocations as recursive") did for rustc. Besides the $(call if_changed,gzip) rules, this covers those that choose gzip through a variable: the built-in initramfs in usr/Makefile (which rcutorture's kvm.sh builds), the EFI zboot image, and the arm, csky, and mips compressed images. As with rustc, "make -n" now runs these recipes, including those that choose through a variable when they select another compressor. Also update the gzip entry in Documentation/kbuild/makefiles.rst, as suggested in review. Tested with GNU Make 4.3 plus CentOS Stream 9's make-4.3-cloexec.patch, GCC 16.2.0, and -j64, on ARCH=x86_64, arm64, and arm defconfig, and ARCH=x86_64 defconfig with a built-in initramfs: none of them prints the warning, and pigz gets the free job slots (-p 54, 64, and 50 for the kernel image). Without this change, the x86_64 builds print it for the kernel image and for the initramfs. Reported-by: Paul E. McKenney Closes: https://lore.kernel.org/all/0ddb5e5e-f801-444c-aa98-0cfe814a3026@paulmck-laptop/ Fixes: 48ebe1cbbeb51 ("kbuild: compress the kernel with pigz if available") Assisted-by: LLM Reviewed-by: Nicolas Schier Acked-by: Lorenzo Stoakes (ARM) Signed-off-by: Kees Cook Tested-by: Paul E. McKenney Link: https://patch.msgid.link/20261010081914.540611-1-kees@kernel.org Signed-off-by: Nathan Chancellor --- Documentation/kbuild/makefiles.rst | 7 +++++-- arch/alpha/boot/Makefile | 4 ++-- arch/arc/boot/Makefile | 2 +- arch/arm/boot/compressed/Makefile | 2 +- arch/arm64/boot/Makefile | 2 +- arch/csky/boot/Makefile | 2 +- arch/microblaze/boot/Makefile | 2 +- arch/mips/boot/Makefile | 2 +- arch/mips/boot/compressed/Makefile | 2 +- arch/nios2/boot/Makefile | 2 +- arch/parisc/boot/compressed/Makefile | 2 +- arch/riscv/boot/Makefile | 2 +- arch/s390/boot/Makefile | 2 +- arch/sh/boot/Makefile | 2 +- arch/sh/boot/compressed/Makefile | 2 +- arch/sparc/boot/Makefile | 4 ++-- arch/x86/boot/compressed/Makefile | 2 +- arch/xtensa/boot/Makefile | 2 +- drivers/firmware/efi/libstub/Makefile.zboot | 2 +- usr/Makefile | 2 +- 20 files changed, 26 insertions(+), 23 deletions(-) diff --git a/Documentation/kbuild/makefiles.rst b/Documentation/kbuild/makefiles.rst index 7521cae7d56f..535d30eef59a 100644 --- a/Documentation/kbuild/makefiles.rst +++ b/Documentation/kbuild/makefiles.rst @@ -1452,13 +1452,16 @@ objcopy OBJCOPYFLAGS_$@ may be used to set additional options. gzip - Compress target. Use maximum compression to compress target. + Compress target. Use maximum compression to compress target. Prefix the + recipe line with '+' so that GNU Make passes it the jobserver, which + scripts/jobserver-exec needs to run pigz (if available) on the free job + slots. Example:: #arch/x86/boot/compressed/Makefile $(obj)/vmlinux.bin.gz: $(vmlinux.bin.all-y) FORCE - $(call if_changed,gzip) + +$(call if_changed,gzip) dtc Create flattened device tree blob object suitable for linking diff --git a/arch/alpha/boot/Makefile b/arch/alpha/boot/Makefile index d8dba85e606c..b050753e9015 100644 --- a/arch/alpha/boot/Makefile +++ b/arch/alpha/boot/Makefile @@ -40,7 +40,7 @@ endif # Compressed kernel image $(obj)/vmlinux.gz: $(obj)/vmlinux FORCE - $(call if_changed,gzip) + +$(call if_changed,gzip) @echo ' Kernel $@ is ready' $(obj)/main.o: $(obj)/ksize.h @@ -85,7 +85,7 @@ $(obj)/vmlinux.nh: vmlinux $(OBJSTRIP) FORCE $(call if_changed,objstrip) $(obj)/vmlinux.nh.gz: $(obj)/vmlinux.nh FORCE - $(call if_changed,gzip) + +$(call if_changed,gzip) $(obj)/tools/lxboot: $(obj)/bootloader $(OBJSTRIP) FORCE $(call if_changed,objstrip) diff --git a/arch/arc/boot/Makefile b/arch/arc/boot/Makefile index 5a8550124b73..8a44c3e7ff3e 100644 --- a/arch/arc/boot/Makefile +++ b/arch/arc/boot/Makefile @@ -23,7 +23,7 @@ $(obj)/vmlinux.bin: vmlinux FORCE $(call if_changed,objcopy) $(obj)/vmlinux.bin.gz: $(obj)/vmlinux.bin FORCE - $(call if_changed,gzip) + +$(call if_changed,gzip) $(obj)/vmlinux.bin.lzma: $(obj)/vmlinux.bin FORCE $(call if_changed,lzma) diff --git a/arch/arm/boot/compressed/Makefile b/arch/arm/boot/compressed/Makefile index e3f550d62857..5f0a1a2e915a 100644 --- a/arch/arm/boot/compressed/Makefile +++ b/arch/arm/boot/compressed/Makefile @@ -153,7 +153,7 @@ $(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/$(HEAD) $(obj)/piggy.o \ @$(check_for_bad_syms) $(obj)/piggy_data: $(obj)/../Image FORCE - $(call if_changed,$(compress-y)) + +$(call if_changed,$(compress-y)) $(obj)/piggy.o: $(obj)/piggy_data diff --git a/arch/arm64/boot/Makefile b/arch/arm64/boot/Makefile index b5a08333bc57..d42d07667822 100644 --- a/arch/arm64/boot/Makefile +++ b/arch/arm64/boot/Makefile @@ -26,7 +26,7 @@ $(obj)/Image.bz2: $(obj)/Image FORCE $(call if_changed,bzip2) $(obj)/Image.gz: $(obj)/Image FORCE - $(call if_changed,gzip) + +$(call if_changed,gzip) $(obj)/Image.lz4: $(obj)/Image FORCE $(call if_changed,lz4) diff --git a/arch/csky/boot/Makefile b/arch/csky/boot/Makefile index c3cfde28f8e6..5d88dc31877b 100644 --- a/arch/csky/boot/Makefile +++ b/arch/csky/boot/Makefile @@ -12,7 +12,7 @@ compress-$(CONFIG_KERNEL_XZ) = xzkern compress-$(CONFIG_KERNEL_LZ4) = lz4 $(obj)/zImage: $(obj)/Image FORCE - $(call if_changed,$(compress-y)) + +$(call if_changed,$(compress-y)) @echo ' Kernel: $@ is ready' UIMAGE_ARCH = sandbox diff --git a/arch/microblaze/boot/Makefile b/arch/microblaze/boot/Makefile index 2b42c370d574..e3df3a64808a 100644 --- a/arch/microblaze/boot/Makefile +++ b/arch/microblaze/boot/Makefile @@ -14,7 +14,7 @@ $(obj)/linux.bin.ub: $(obj)/linux.bin FORCE $(call if_changed,uimage) $(obj)/linux.bin.gz: $(obj)/linux.bin FORCE - $(call if_changed,gzip) + +$(call if_changed,gzip) quiet_cmd_strip = STRIP $< $@$2 cmd_strip = $(STRIP) -K microblaze_start -K _end -K __log_buf \ diff --git a/arch/mips/boot/Makefile b/arch/mips/boot/Makefile index 8473c4671702..996bf41b78de 100644 --- a/arch/mips/boot/Makefile +++ b/arch/mips/boot/Makefile @@ -63,7 +63,7 @@ $(obj)/vmlinux.bin.bz2: $(obj)/vmlinux.bin FORCE $(call if_changed,bzip2) $(obj)/vmlinux.bin.gz: $(obj)/vmlinux.bin FORCE - $(call if_changed,gzip) + +$(call if_changed,gzip) $(obj)/vmlinux.bin.lzma: $(obj)/vmlinux.bin FORCE $(call if_changed,lzma) diff --git a/arch/mips/boot/compressed/Makefile b/arch/mips/boot/compressed/Makefile index e0b8ec9a9516..2684540758a8 100644 --- a/arch/mips/boot/compressed/Makefile +++ b/arch/mips/boot/compressed/Makefile @@ -69,7 +69,7 @@ tool_$(CONFIG_KERNEL_ZSTD) = zstd22_with_size targets += vmlinux.bin.z $(obj)/vmlinux.bin.z: $(obj)/vmlinux.bin FORCE - $(call if_changed,$(tool_y)) + +$(call if_changed,$(tool_y)) targets += piggy.o dummy.o diff --git a/arch/nios2/boot/Makefile b/arch/nios2/boot/Makefile index 29c11a06b750..1381e6e428a8 100644 --- a/arch/nios2/boot/Makefile +++ b/arch/nios2/boot/Makefile @@ -18,7 +18,7 @@ $(obj)/vmlinux.bin: vmlinux FORCE $(call if_changed,objcopy) $(obj)/vmlinux.gz: $(obj)/vmlinux.bin FORCE - $(call if_changed,gzip) + +$(call if_changed,gzip) $(obj)/vmImage: $(obj)/vmlinux.gz FORCE $(call if_changed,uimage) diff --git a/arch/parisc/boot/compressed/Makefile b/arch/parisc/boot/compressed/Makefile index 14eefb5ed5d1..a7fc7d424c00 100644 --- a/arch/parisc/boot/compressed/Makefile +++ b/arch/parisc/boot/compressed/Makefile @@ -55,7 +55,7 @@ suffix-$(CONFIG_KERNEL_LZO) := lzo suffix-$(CONFIG_KERNEL_XZ) := xz $(obj)/vmlinux.bin.gz: $(obj)/vmlinux.bin FORCE - $(call if_changed,gzip) + +$(call if_changed,gzip) $(obj)/vmlinux.bin.bz2: $(obj)/vmlinux.bin FORCE $(call if_changed,bzip2_with_size) $(obj)/vmlinux.bin.lz4: $(obj)/vmlinux.bin FORCE diff --git a/arch/riscv/boot/Makefile b/arch/riscv/boot/Makefile index 4a2d75b7e42d..b80a077a8e8f 100644 --- a/arch/riscv/boot/Makefile +++ b/arch/riscv/boot/Makefile @@ -23,7 +23,7 @@ $(obj)/Image: vmlinux FORCE $(call if_changed,objcopy) $(obj)/Image.gz: $(obj)/Image FORCE - $(call if_changed,gzip) + +$(call if_changed,gzip) $(obj)/loader.o: $(src)/loader.S $(obj)/Image diff --git a/arch/s390/boot/Makefile b/arch/s390/boot/Makefile index 10b75e053a6f..e8e43ae80dd5 100644 --- a/arch/s390/boot/Makefile +++ b/arch/s390/boot/Makefile @@ -117,7 +117,7 @@ suffix-$(CONFIG_KERNEL_XZ) := .xz suffix-$(CONFIG_KERNEL_ZSTD) := .zst $(obj)/vmlinux.bin.gz: $(obj)/vmlinux.bin FORCE - $(call if_changed,gzip) + +$(call if_changed,gzip) $(obj)/vmlinux.bin.bz2: $(obj)/vmlinux.bin FORCE $(call if_changed,bzip2_with_size) $(obj)/vmlinux.bin.lz4: $(obj)/vmlinux.bin FORCE diff --git a/arch/sh/boot/Makefile b/arch/sh/boot/Makefile index 1f5d2df3c7e0..cf63e319b786 100644 --- a/arch/sh/boot/Makefile +++ b/arch/sh/boot/Makefile @@ -66,7 +66,7 @@ $(obj)/vmlinux.bin: vmlinux FORCE $(call if_changed,objcopy) $(obj)/vmlinux.bin.gz: $(obj)/vmlinux.bin FORCE - $(call if_changed,gzip) + +$(call if_changed,gzip) $(obj)/vmlinux.bin.bz2: $(obj)/vmlinux.bin FORCE $(call if_changed,bzip2) diff --git a/arch/sh/boot/compressed/Makefile b/arch/sh/boot/compressed/Makefile index 58df491778b2..b85aa5f0d616 100644 --- a/arch/sh/boot/compressed/Makefile +++ b/arch/sh/boot/compressed/Makefile @@ -39,7 +39,7 @@ $(obj)/vmlinux.bin: vmlinux FORCE $(call if_changed,objcopy) $(obj)/vmlinux.bin.gz: $(obj)/vmlinux.bin FORCE - $(call if_changed,gzip) + +$(call if_changed,gzip) $(obj)/vmlinux.bin.bz2: $(obj)/vmlinux.bin FORCE $(call if_changed,bzip2_with_size) $(obj)/vmlinux.bin.lzma: $(obj)/vmlinux.bin FORCE diff --git a/arch/sparc/boot/Makefile b/arch/sparc/boot/Makefile index 339c42d35089..b53cf596611e 100644 --- a/arch/sparc/boot/Makefile +++ b/arch/sparc/boot/Makefile @@ -23,7 +23,7 @@ ifeq ($(CONFIG_SPARC64),y) # Actual linking $(obj)/zImage: $(obj)/image FORCE - $(call if_changed,gzip) + +$(call if_changed,gzip) @$(kecho) 'Kernel: $@ is ready' '(#'$(or $(KBUILD_BUILD_VERSION),`cat .version`)')' $(obj)/vmlinux.aout: vmlinux FORCE @@ -45,7 +45,7 @@ $(obj)/image.bin: $(obj)/image FORCE $(call if_changed,objcopy) $(obj)/image.gz: $(obj)/image.bin FORCE - $(call if_changed,gzip) + +$(call if_changed,gzip) UIMAGE_LOADADDR = $(CONFIG_UBOOT_LOAD_ADDR) UIMAGE_ENTRYADDR = $(CONFIG_UBOOT_ENTRY_ADDR) diff --git a/arch/x86/boot/compressed/Makefile b/arch/x86/boot/compressed/Makefile index 6ec5e031db1d..680ad803cba7 100644 --- a/arch/x86/boot/compressed/Makefile +++ b/arch/x86/boot/compressed/Makefile @@ -133,7 +133,7 @@ vmlinux.bin.all-y := $(obj)/vmlinux.bin vmlinux.bin.all-$(CONFIG_X86_NEED_RELOCS) += $(obj)/vmlinux.relocs $(obj)/vmlinux.bin.gz: $(vmlinux.bin.all-y) FORCE - $(call if_changed,gzip) + +$(call if_changed,gzip) $(obj)/vmlinux.bin.bz2: $(vmlinux.bin.all-y) FORCE $(call if_changed,bzip2_with_size) $(obj)/vmlinux.bin.lzma: $(vmlinux.bin.all-y) FORCE diff --git a/arch/xtensa/boot/Makefile b/arch/xtensa/boot/Makefile index d8b0fadf429a..934f9015b15f 100644 --- a/arch/xtensa/boot/Makefile +++ b/arch/xtensa/boot/Makefile @@ -36,7 +36,7 @@ $(obj)/vmlinux.bin: vmlinux FORCE $(call if_changed,objcopy) $(obj)/vmlinux.bin.gz: $(obj)/vmlinux.bin FORCE - $(call if_changed,gzip) + +$(call if_changed,gzip) boot-elf: $(obj)/vmlinux.bin boot-redboot: $(obj)/vmlinux.bin.gz diff --git a/drivers/firmware/efi/libstub/Makefile.zboot b/drivers/firmware/efi/libstub/Makefile.zboot index 832deee36e48..52baacf3e9d9 100644 --- a/drivers/firmware/efi/libstub/Makefile.zboot +++ b/drivers/firmware/efi/libstub/Makefile.zboot @@ -24,7 +24,7 @@ zboot-method-$(CONFIG_KERNEL_ZSTD) := zstd22_with_size zboot-size-len-$(CONFIG_KERNEL_ZSTD) := 4 $(obj)/vmlinuz: $(obj)/vmlinux.bin FORCE - $(call if_changed,$(zboot-method-y)) + +$(call if_changed,$(zboot-method-y)) # avoid eager evaluation to prevent references to non-existent build artifacts OBJCOPYFLAGS_vmlinuz.o = -I binary -O $(EFI_ZBOOT_BFD_TARGET) $(EFI_ZBOOT_OBJCOPY_FLAGS) \ diff --git a/usr/Makefile b/usr/Makefile index e8f42478a0b7..a45236926228 100644 --- a/usr/Makefile +++ b/usr/Makefile @@ -78,7 +78,7 @@ $(obj)/initramfs_data.cpio: $(src)/gen_initramfs.sh $(obj)/gen_init_cpio $(deps_ endif $(obj)/initramfs_inc_data: $(cpio-data) FORCE - $(call if_changed,$(compress-y)) + +$(call if_changed,$(compress-y)) targets += initramfs_data.cpio initramfs_inc_data From ae5f6044c4cdffe2b3361b15c1d6fc67b339f2a4 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Sat, 10 Oct 2026 01:19:11 -0700 Subject: [PATCH 58/59] kbuild: Mark the initcall ordering recipe as recursive generate_initcall_order.pl, which orders initcalls for Clang LTO, forks a child per input file, up to the job count scripts/jobserver-exec passes it in PARALLELISM. It used to run from link-vmlinux.sh, which the top-level Makefile invokes as a recursive recipe, but commit 5d45950dfbb15 ("kbuild: move vmlinux.o link to scripts/Makefile.vmlinux_o") moved it into a recipe without "+". GNU Make passes the jobserver pipe only to recipes it treats as recursive, and the make 4.3 in CentOS Stream 9 also hides it in sub-makes (a backport of the fix for GNU Make bug 58232), so jobserver-exec cannot open it and prints: WARNING: Unable to reopen jobserver read-side pipe: FileNotFoundError(2, 'No such file or directory') PARALLELISM is then left unset, and the script caps its children at the number of online CPUs instead of the free job slots. Mark the recipe as recursive again (this makes "make -n" runs the script again too, as it did from link-vmlinux.sh). Masahiro posted the same change and withdrew it because the script was given only vmlinux.a, and so never ran more than one child [1]. But on arm64, LoongArch, and RISC-V with CONFIG_EFI_STUB, KBUILD_VMLINUX_LIBS also gives it the EFI stub's lib.a. Either way, the warning is silenced. Tested ARCH=x86_64 defconfig with CONFIG_LTO_CLANG_THIN=y, LLVM=1 (Clang 24.0.0git), and GNU Make 4.3 plus CentOS Stream 9's make-4.3-cloexec.patch. Fixes: 5d45950dfbb15 ("kbuild: move vmlinux.o link to scripts/Makefile.vmlinux_o") Link: https://lore.kernel.org/all/CAK7LNARCM=rUm8mA8GRQ7ufeyfneGf4OEvHmESKt=zuxs2KrHw@mail.gmail.com/ [1] Assisted-by: LLM Reviewed-by: Nicolas Schier Signed-off-by: Kees Cook Tested-by: Paul E. McKenney Link: https://patch.msgid.link/20261010081914.540611-2-kees@kernel.org Signed-off-by: Nathan Chancellor --- scripts/Makefile.vmlinux_o | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/Makefile.vmlinux_o b/scripts/Makefile.vmlinux_o index 24a3a4fd271c..0b37bd5dc3a4 100644 --- a/scripts/Makefile.vmlinux_o +++ b/scripts/Makefile.vmlinux_o @@ -19,7 +19,7 @@ quiet_cmd_gen_initcalls_lds = GEN $@ .tmp_initcalls.lds: $(srctree)/scripts/generate_initcall_order.pl \ vmlinux.a $(KBUILD_VMLINUX_LIBS) FORCE - $(call if_changed,gen_initcalls_lds) + +$(call if_changed,gen_initcalls_lds) targets := .tmp_initcalls.lds From 2b358510d5cc56164f83110ca1499c95a5a352cd Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Wed, 14 Oct 2026 16:40:41 -0700 Subject: [PATCH 59/59] kbuild: AutoFDO: Update split machine function argument for upstream rename A recent LLVM commit [1] renamed the internal LLVM plugin option '-split-machine-functions' to '-function-splitting=all', breaking the AutoFDO + ThinLTO build with: ld.lld: error: -plugin-opt=-: ld.lld: Unknown command line argument '-split-machine-functions'. Try: 'ld.lld --help' The plugin option is needed for in-process ThinLTO, so handle the rename accordingly using ld-option. Cc: stable@vger.kernel.org Closes: https://github.com/ClangBuiltLinux/linux/issues/2186 Fixes: 2fd65f7afd5a ("AutoFDO: Enable machine function split optimization for AutoFDO") Link: https://github.com/llvm/llvm-project/commit/3e6acc87c58843e551d6e8e4bf06c26712b04622 [1] Link: https://patch.msgid.link/20261014-update-autofdo-split-machine-function-option-v1-1-5c71b3005e89@kernel.org Signed-off-by: Nathan Chancellor --- scripts/Makefile.autofdo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/Makefile.autofdo b/scripts/Makefile.autofdo index 1442043da139..57a3aba6ffc2 100644 --- a/scripts/Makefile.autofdo +++ b/scripts/Makefile.autofdo @@ -22,7 +22,7 @@ ifdef CONFIG_LTO_CLANG_THIN KBUILD_LDFLAGS += --lto-sample-profile=$(CLANG_AUTOFDO_PROFILE) endif KBUILD_LDFLAGS += --mllvm=-enable-fs-discriminator=true --mllvm=-improved-fs-discriminator=true -plugin-opt=thinlto - KBUILD_LDFLAGS += -plugin-opt=-split-machine-functions + KBUILD_LDFLAGS += $(call ld-option,-plugin-opt=-function-splitting=all,-plugin-opt=-split-machine-functions) endif export CFLAGS_AUTOFDO_CLANG RUSTFLAGS_AUTOFDO_CLANG