From 5fe4dd59664105a9ea1a9468aa7391806b80364a Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Wed, 23 Sep 2026 18:17:50 +0100 Subject: [PATCH 01/16] 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 02/16] 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 03/16] 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 04/16] 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 05/16] 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 06/16] 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 07/16] 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 08/16] 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 09/16] 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 10/16] 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 11/16] 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 12/16] 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 c4c24fd63a8d56cfefd78678758f9be72e17c90a Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Wed, 7 Oct 2026 17:22:21 +0200 Subject: [PATCH 13/16] 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 14/16] 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 0d4d70da348b066b02f5aa4b893b13c4e32dc838 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Sat, 10 Oct 2026 01:19:10 -0700 Subject: [PATCH 15/16] 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 16/16] 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