From: "Lorenzo Stoakes (ARM)" <ljs@kernel.org>
To: "Linus Torvalds" <torvalds@linux-foundation.org>,
"Nathan Chancellor" <nathan@kernel.org>,
"Nicolas Schier" <nsc@kernel.org>,
"Nick Desaulniers" <ndesaulniers@google.com>,
"Bill Wendling" <morbo@google.com>,
"Justin Stitt" <justinstitt@google.com>,
"Masahiro Yamada" <masahiroy@kernel.org>,
"Alexey Gladkov" <legion@kernel.org>,
"Thomas Gleixner" <tglx@kernel.org>,
"Ingo Molnar" <mingo@redhat.com>,
"Borislav Petkov" <bp@alien8.de>,
"Dave Hansen" <dave.hansen@linux.intel.com>,
x86@kernel.org, "H. Peter Anvin" <hpa@zytor.com>,
"Paul Walmsley" <pjw@kernel.org>,
"Palmer Dabbelt" <palmer@dabbelt.com>,
"Albert Ou" <aou@eecs.berkeley.edu>,
"Alexandre Ghiti" <alex@ghiti.fr>,
"Arnd Bergmann" <arnd@arndb.de>,
"Catalin Marinas" <catalin.marinas@arm.com>,
"Will Deacon" <will@kernel.org>,
"Mark Rutland" <mark.rutland@arm.com>,
"Ard Biesheuvel" <ardb@kernel.org>,
"Ilias Apalodimas" <ilias.apalodimas@linaro.org>,
"Josh Poimboeuf" <jpoimboe@kernel.org>,
"Peter Zijlstra" <peterz@infradead.org>,
"Miguel Ojeda" <ojeda@kernel.org>,
"Boqun Feng" <boqun@kernel.org>, "Gary Guo" <gary@garyguo.net>,
"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
"Benno Lossin" <lossin@kernel.org>,
"Andreas Hindborg" <a.hindborg@kernel.org>,
"Alice Ryhl" <aliceryhl@google.com>,
"Trevor Gross" <tmgross@umich.edu>,
"Danilo Krummrich" <dakr@kernel.org>,
"Daniel Almeida" <daniel.almeida@collabora.com>,
"Tamir Duberstein" <tamird@kernel.org>,
"Alexandre Courbot" <acourbot@nvidia.com>,
"Onur Özkan" <work@onurozkan.dev>,
"Jonathan Corbet" <corbet@lwn.net>,
"Randy Dunlap" <rdunlap@infradead.org>,
"Kees Cook" <kees@kernel.org>,
"Gustavo A. R. Silva" <gustavoars@kernel.org>
Cc: linux-kbuild@vger.kernel.org, linux-kernel@vger.kernel.org,
llvm@lists.linux.dev, linux-riscv@lists.infradead.org,
linux-arch@vger.kernel.org,
linux-arm-kernel@lists.infradead.org, linux-efi@vger.kernel.org,
rust-for-linux@vger.kernel.org, linux-doc@vger.kernel.org,
Jens Axboe <axboe@kernel.dk>,
linux-hardening@vger.kernel.org,
"Lorenzo Stoakes (ARM)" <ljs@kernel.org>
Subject: [PATCH v2 13/21] modpost: emit module descriptors as assembly
Date: Mon, 14 Sep 2026 10:22:12 +0100 [thread overview]
Message-ID: <20260914-build-speedup-v2-13-39817ec5db23@kernel.org> (raw)
In-Reply-To: <20260914-build-speedup-v2-0-39817ec5db23@kernel.org>
modpost generates a descriptor for every module in the form of a
<module>.mod.c file with .modinfo strings, the __this_module descriptor,
exported symbol tables and (with CONFIG_MODVERSIONS set), the CRC of
imported symbols.
These files are compiled like any other kernel C file with all of the
-include preamble, as well as including linux/module.h, header dependencies
generated by fixdep of a few hundred headers, an objtool run and if LTO is
being performed, a link is performed to generate native code.
On an x86-64 allmodconfig build 11,189 *.mod.c files are built, each
taking ~0.24s of CPU time to compile, and module finalisation as a whole
6,300 CPU seconds, or 64 seconds of wall time when run over 128 threads.
It also generates ~1.3 GiB of *.mod.o.cmd files that every subsequent build
has to read back.
Avoid all this by emitting the descriptors as assembly instead.
The layout required (size and alignment of struct module, struct
modversion_info, the module's name offsets, init, and exit fields and
whether the architecture uses PREL32 ksymtab references) can all be derived
from scripts/mod/module-offsets.h.
The fields of __this_module are emitted in offset order rather than
declaration order, as CONFIG_RANDSTRUCT shuffles struct module so the name,
init and exit fields can land anywhere.
An assembly file avoids all of the issues previously mentioned so this
conversion results in a very significant performance win on kernel build.
As a consequence of this change, since module-offsets.c includes
linux/module.h, scripts/mod is now built after the generated headers in
prepare0, rather than before.
Also update .gitignore and make clean to handle .mod.S files, but keep
.mod.c files there to ensure that users do not end up with untracked
changes/dirty trees after the change takes effect.
The sections were confirmed to be byte-for-byte identical to the C version
produced - each of .modinfo, .gnu.linkonce.this_module, __ksymtab*,
__ksymtab_strings, __kcrctab*, __kflagstab*, __versions,
__version_ext_crcs, __version_ext_names and their relocations - for all
8,135 modules of a clang allmodconfig build with CONFIG_COMPILE_TEST off
and CONFIG_MODVERSIONS, CONFIG_EXTENDED_MODVERSIONS and
CONFIG_MODULE_SRCVERSION_ALL on, and for a sample built with gcc.
What differs is what the compiler added around them: the __UNIQUE_ID_*
locals, the KASAN constructor for the .mod.c globals, and on x86 a
.note.gnu.property that the linker already drops from any module containing
an assembly file and the loader never reads.
None of these have any impact on the build, however.
x86_64 kernels built with gcc and clang, with CONFIG_MODVERSIONS,
CONFIG_EXTENDED_MODVERSIONS and CONFIG_MODULE_SRCVERSION_ALL, were booted,
every module and an external one loaded and unloaded, and the srcversions
checked against modinfo, i386 and arm64 defconfigs (gcc and clang) and a
ThinLTO build were also built as part of testing.
An x86_64 CONFIG_RANDSTRUCT_FULL build with clang, which places the name
field after init and exit, was checked the same way and produces the same
.gnu.linkonce.this_module sections and relocations as the C version.
On the x86_64 allmodconfig with clang 22, "make modules" with every
*.mod.o and *.ko deleted goes from 64.5s (6,306 CPU-s) to 28.5s (518
CPU-s).
A consequence of this change is that the make jobs are now so small that
make cannot dispatch them quick enough, however the next commit in the
series addresses this issue.
This impacts clean and no-op builds with a large number of modules most
noticeably.
Whole build, 128-thread Threadripper 9980X, best of N runs:
before after delta
-------------------------------
x86 allmodconfig, no-op make, gcc 12.7s 2.3s -10.4s (-82%)
x86 allmodconfig, no-op make, clang 13.9s 2.8s -11.2s (-80%)
x86 allmodconfig, clean, gcc 342.6s 306.6s -36.0s (-11%)
x86 allmodconfig, clean, clang 340.1s 290.2s -49.8s (-15%)
Assisted-by: LLM
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
.gitignore | 1 +
Makefile | 4 +-
include/linux/vermagic.h | 2 +-
scripts/Makefile.modfinal | 10 +-
scripts/Makefile.modpost | 2 +-
scripts/mod/.gitignore | 1 +
scripts/mod/Makefile | 8 +
scripts/mod/modpost.c | 637 ++++++++++++++++++++++++++++++++-----------
scripts/mod/module-offsets.c | 35 +++
scripts/tags.sh | 5 +-
10 files changed, 530 insertions(+), 175 deletions(-)
diff --git a/.gitignore b/.gitignore
index 9875120ea7bd..00fc262b894b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,6 +38,7 @@
*.lzo
*.mod
*.mod.c
+*.mod.S
*.o
*.o.*
*.patch
diff --git a/Makefile b/Makefile
index 68df3446d111..8263fc8a86b3 100644
--- a/Makefile
+++ b/Makefile
@@ -1422,8 +1422,8 @@ archprepare: outputmakefile archheaders archscripts scripts include/config/kerne
include/generated/rustc_cfg remove-stale-files
prepare0: archprepare
- $(Q)$(MAKE) $(build)=scripts/mod
$(Q)$(MAKE) $(build)=. prepare
+ $(Q)$(MAKE) $(build)=scripts/mod
# All the preparing..
prepare: prepare0
@@ -2245,7 +2245,7 @@ clean: $(clean-dirs)
-o -name '*.dt.yaml' -o -name 'dtbs-list' \
-o -name '*.dwo' -o -name '*.lst' \
-o -name '*.su' -o -name '*.mod' \
- -o -name '.*.d' -o -name '.*.tmp' -o -name '*.mod.c' \
+ -o -name '.*.d' -o -name '.*.tmp' -o -name '*.mod.c' -o -name '*.mod.S' \
-o -name '*.lex.c' -o -name '*.tab.[ch]' \
-o -name '*.asn1.[ch]' \
-o -name '*.symtypes' -o -name 'modules.order' \
diff --git a/include/linux/vermagic.h b/include/linux/vermagic.h
index 335c360d4f9b..09f05d02664c 100644
--- a/include/linux/vermagic.h
+++ b/include/linux/vermagic.h
@@ -3,7 +3,7 @@
#define _LINUX_VERMAGIC_H
#ifndef INCLUDE_VERMAGIC
-#error "This header can be included from kernel/module.c or *.mod.c only"
+#error "This header can be included from kernel/module.c or scripts/module-common.c only"
#endif
#include <generated/utsrelease.h>
diff --git a/scripts/Makefile.modfinal b/scripts/Makefile.modfinal
index 01a37ec872b9..75e9effdf02c 100644
--- a/scripts/Makefile.modfinal
+++ b/scripts/Makefile.modfinal
@@ -20,10 +20,14 @@ __modfinal: $(modules:%.o=%.ko)
modname = $(notdir $(@:.mod.o=))
part-of-module = y
GCOV_PROFILE := n
-ccflags-remove-y := $(CC_FLAGS_CFI)
-%.mod.o: %.mod.c FORCE
- $(call if_changed_rule,cc_o_c)
+# modpost lays the <module>.mod.S out completely (write_mod_S_file()), so it
+# needs only the assembler and no dependency tracking.
+quiet_cmd_as_mod_o = AS [M] $@
+ cmd_as_mod_o = $(CC) $(_a_flags) $(modkern_aflags) -c -o $@ $<
+
+%.mod.o: %.mod.S FORCE
+ $(call if_changed,as_mod_o)
.module-common.o: $(srctree)/scripts/module-common.c FORCE
$(call if_changed_rule,cc_o_c)
diff --git a/scripts/Makefile.modpost b/scripts/Makefile.modpost
index d7d45067d08b..eecf5f5c99b4 100644
--- a/scripts/Makefile.modpost
+++ b/scripts/Makefile.modpost
@@ -13,7 +13,7 @@
# Stage 2 is handled by this file and does the following
# 1) Find all modules listed in modules.order
# 2) modpost is then used to
-# 3) create one <module>.mod.c file per module
+# 3) create one <module>.mod.S file per module
# 4) create one Module.symvers file with CRC for all exported symbols
# Step 3 is used to place certain information in the module's ELF
diff --git a/scripts/mod/.gitignore b/scripts/mod/.gitignore
index 0465ec33c9bf..620ab4362094 100644
--- a/scripts/mod/.gitignore
+++ b/scripts/mod/.gitignore
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-only
/devicetable-offsets.h
+/module-offsets.h
/elfconfig.h
/mk_elfconfig
/modpost
diff --git a/scripts/mod/Makefile b/scripts/mod/Makefile
index c729bc936bae..fbd5099e0441 100644
--- a/scripts/mod/Makefile
+++ b/scripts/mod/Makefile
@@ -13,10 +13,18 @@ $(obj)/$(devicetable-offsets-file): $(obj)/devicetable-offsets.s FORCE
targets += $(devicetable-offsets-file) devicetable-offsets.s
+module-offsets-file := module-offsets.h
+
+$(obj)/$(module-offsets-file): $(obj)/module-offsets.s FORCE
+ $(call filechk,offsets,__MODULE_OFFSETS_H__)
+
+targets += $(module-offsets-file) module-offsets.s
+
# dependencies on generated files need to be listed explicitly
$(obj)/modpost.o $(obj)/file2alias.o $(obj)/sumversion.o $(obj)/symsearch.o: $(obj)/elfconfig.h
$(obj)/file2alias.o: $(obj)/$(devicetable-offsets-file)
+$(obj)/modpost.o: $(obj)/$(module-offsets-file)
quiet_cmd_elfconfig = MKELF $@
cmd_elfconfig = $(obj)/mk_elfconfig < $< > $@
diff --git a/scripts/mod/modpost.c b/scripts/mod/modpost.c
index 0fd43c8a89ea..b4550b545330 100644
--- a/scripts/mod/modpost.c
+++ b/scripts/mod/modpost.c
@@ -26,6 +26,7 @@
#include <list.h>
#include <xalloc.h>
#include "modpost.h"
+#include "module-offsets.h"
#include "../../include/linux/license.h"
#define MODULE_NS_PREFIX "module:"
@@ -1920,39 +1921,6 @@ static void check_modname_len(struct module *mod)
mod_error(mod, "module name is too long\n");
}
-/**
- * Header for the generated file
- **/
-static void add_header(struct buffer *b, struct module *mod)
-{
- buf_printf(b, "#include <linux/module.h>\n");
- buf_printf(b, "#include <linux/export-internal.h>\n");
- buf_printf(b, "#include <linux/compiler.h>\n");
- buf_printf(b, "\n");
- buf_printf(b, "MODULE_INFO(name, KBUILD_MODNAME);\n");
- buf_printf(b, "\n");
- buf_printf(b, "__visible struct module __this_module\n");
- buf_printf(b, "__section(\".gnu.linkonce.this_module\") = {\n");
- buf_printf(b, "\t.name = KBUILD_MODNAME,\n");
- if (mod->has_init)
- buf_printf(b, "\t.init = init_module,\n");
- if (mod->has_cleanup)
- buf_printf(b, "#ifdef CONFIG_MODULE_UNLOAD\n"
- "\t.exit = cleanup_module,\n"
- "#endif\n");
- buf_printf(b, "\t.arch = MODULE_ARCH_INIT,\n");
- buf_printf(b, "};\n");
-
- if (!external_module)
- buf_printf(b, "\nMODULE_INFO(intree, \"Y\");\n");
-
- if (strstarts(mod->name, "drivers/staging"))
- buf_printf(b, "\nMODULE_INFO(staging, \"Y\");\n");
-
- if (strstarts(mod->name, "tools/testing"))
- buf_printf(b, "\nMODULE_INFO(test, \"Y\");\n");
-}
-
static void add_exported_symbols(struct buffer *buf, struct module *mod)
{
struct symbol *sym;
@@ -1990,123 +1958,6 @@ static void add_exported_symbols(struct buffer *buf, struct module *mod)
}
}
-/**
- * Record CRCs for unresolved symbols, supporting long names
- */
-static void add_extended_versions(struct buffer *b, struct module *mod)
-{
- struct symbol *s;
-
- if (!extended_modversions)
- return;
-
- buf_printf(b, "\n");
- buf_printf(b, "static const u32 ____version_ext_crcs[]\n");
- buf_printf(b, "__used __section(\"__version_ext_crcs\") = {\n");
- list_for_each_entry(s, &mod->unresolved_symbols, list) {
- if (!s->module)
- continue;
- if (!s->crc_valid) {
- mod_warn(mod, "symbol '%s' has no CRC!\n", s->name);
- continue;
- }
- buf_printf(b, "\t0x%08x,\n", s->crc);
- }
- buf_printf(b, "};\n");
-
- buf_printf(b, "static const char ____version_ext_names[]\n");
- buf_printf(b, "__used __section(\"__version_ext_names\") =\n");
- list_for_each_entry(s, &mod->unresolved_symbols, list) {
- if (!s->module)
- continue;
- if (!s->crc_valid)
- /*
- * We already warned on this when producing the crc
- * table.
- * We need to skip its name too, as the indexes in
- * both tables need to align.
- */
- continue;
- buf_printf(b, "\t\"%s\\0\"\n", s->name);
- }
- buf_printf(b, ";\n");
-}
-
-/**
- * Record CRCs for unresolved symbols
- **/
-static void add_versions(struct buffer *b, struct module *mod)
-{
- struct symbol *s;
-
- if (!basic_modversions)
- return;
-
- buf_printf(b, "\n");
- buf_printf(b, "static const struct modversion_info ____versions[]\n");
- buf_printf(b, "__used __section(\"__versions\") = {\n");
-
- list_for_each_entry(s, &mod->unresolved_symbols, list) {
- if (!s->module)
- continue;
- if (!s->crc_valid) {
- mod_warn(mod, "symbol '%s' has no CRC!\n", s->name);
- continue;
- }
- if (strlen(s->name) >= MODULE_NAME_LEN) {
- if (extended_modversions) {
- /* this symbol will only be in the extended info */
- continue;
- } else {
- mod_error(mod, "too long symbol '%s'\n", s->name);
- break;
- }
- }
- buf_printf(b, "\t{ 0x%08x, \"%s\" },\n",
- s->crc, s->name);
- }
-
- buf_printf(b, "};\n");
-}
-
-static void add_depends(struct buffer *b, struct module *mod)
-{
- struct symbol *s;
- int first = 1;
-
- /* Clear ->seen flag of modules that own symbols needed by this. */
- list_for_each_entry(s, &mod->unresolved_symbols, list) {
- if (s->module)
- s->module->seen = s->module->is_vmlinux;
- }
-
- buf_printf(b, "\n");
- buf_printf(b, "MODULE_INFO(depends, \"");
- list_for_each_entry(s, &mod->unresolved_symbols, list) {
- const char *p;
- if (!s->module)
- continue;
-
- if (s->module->seen)
- continue;
-
- s->module->seen = true;
- p = get_basename(s->module->name);
- buf_printf(b, "%s%s", first ? "" : ",", p);
- first = 0;
- }
- buf_printf(b, "\");\n");
-}
-
-static void add_srcversion(struct buffer *b, struct module *mod)
-{
- if (mod->srcversion[0]) {
- buf_printf(b, "\n");
- buf_printf(b, "MODULE_INFO(srcversion, \"%s\");\n",
- mod->srcversion);
- }
-}
-
static void write_buf(struct buffer *b, const char *fname)
{
FILE *file;
@@ -2191,30 +2042,483 @@ static void write_vmlinux_export_c_file(struct module *mod)
free(buf.p);
}
-/* do sanity checks, and generate *.mod.c file */
-static void write_mod_c_file(struct module *mod)
+#if MOD_SIZEOF_LONG == 8
+#define MOD_PTR_DIRECTIVE ".quad"
+#else
+#define MOD_PTR_DIRECTIVE ".long"
+#endif
+
+/* See KSYM_FUNC() in include/linux/export-internal.h. */
+#if MOD_FUNC_PLABEL
+#define MOD_FUNC_PREFIX "P%"
+#else
+#define MOD_FUNC_PREFIX ""
+#endif
+
+/* See __KSYM_ALIGN in include/linux/export-internal.h. */
+#if MOD_PREL32_RELOCATIONS || MOD_SIZEOF_LONG == 4
+#define KSYM_ALIGN 4
+#else
+#define KSYM_ALIGN 8
+#endif
+
+/* Append the body of an assembler string literal, escaped as needed. */
+static void buf_escaped(struct buffer *buf, const char *str)
+{
+ unsigned char chr;
+
+ while ((chr = *str++)) {
+ if (chr == '"' || chr == '\\')
+ buf_printf(buf, "\\%c", chr);
+ else if (isprint(chr))
+ buf_printf(buf, "%c", chr);
+ else
+ buf_printf(buf, "\\%03o", chr);
+ }
+}
+
+static void buf_asciz(struct buffer *buf, const char *str)
{
- struct buffer buf = { };
- struct module_alias *alias, *next;
- char fname[PATH_MAX];
- int ret;
+ buf_printf(buf, "\t.asciz \"");
+ buf_escaped(buf, str);
+ buf_printf(buf, "\"\n");
+}
- add_header(&buf, mod);
- add_exported_symbols(&buf, mod);
- add_versions(&buf, mod);
- add_extended_versions(&buf, mod);
- add_depends(&buf, mod);
+/* The equivalent of MODULE_INFO(tag, info). */
+static void add_asm_modinfo(struct buffer *buf, const char *tag,
+ const char *info)
+{
+ buf_printf(buf, "\t.section .modinfo,\"a\",%%progbits\n");
+ buf_printf(buf, "\t.asciz \"%s=", tag);
+ buf_escaped(buf, info);
+ buf_printf(buf, "\"\n");
+}
- buf_printf(&buf, "\n");
+/* See __KSYM_REF() in include/linux/export-internal.h. */
+static void add_asm_ksym_ref(struct buffer *buf, const char *prefix,
+ const char *sym)
+{
+#if MOD_PREL32_RELOCATIONS
+ buf_printf(buf, "\t.long %s%s - .\n", prefix, sym);
+#else
+ buf_printf(buf, "\t" MOD_PTR_DIRECTIVE " %s%s\n", prefix, sym);
+#endif
+}
+
+/* The name and namespace strings a ksymtab entry refers to. */
+static void add_asm_kstrtab(struct buffer *buf, const struct symbol *sym)
+{
+ buf_printf(buf, "\t.section \"__ksymtab_strings\",\"aMS\",%%progbits,1\n");
+ buf_printf(buf, "__kstrtab_%s:\n", sym->name);
+ buf_asciz(buf, sym->name);
+ buf_printf(buf, "__kstrtabns_%s:\n", sym->name);
+ buf_asciz(buf, sym->namespace);
+ buf_printf(buf, "\t.previous\n");
+}
+
+/* The equivalent of SYMBOL_FLAGS(). */
+static void add_asm_kflagstab(struct buffer *buf, const struct symbol *sym)
+{
+ buf_printf(buf, "\t.section \"___kflagstab+%s\", \"a\"\n", sym->name);
+ buf_printf(buf, "__flags_%s:\n", sym->name);
+ buf_printf(buf, "\t.byte 0x%02x\n", get_symbol_flags(sym));
+ buf_printf(buf, "\t.previous\n");
+}
+
+/* The equivalent of KSYMTAB_FUNC()/KSYMTAB_DATA(). */
+static void add_asm_ksymtab(struct buffer *buf, const struct symbol *sym)
+{
+ const char *name = sym->name;
+
+ add_asm_kstrtab(buf, sym);
+
+ buf_printf(buf, "\t.section \"___ksymtab+%s\", \"a\"\n", name);
+ buf_printf(buf, "\t.balign %d\n", KSYM_ALIGN);
+ buf_printf(buf, "__ksymtab_%s:\n", name);
+ add_asm_ksym_ref(buf, sym->is_func ? MOD_FUNC_PREFIX : "", name);
+ add_asm_ksym_ref(buf, "__kstrtab_", name);
+ add_asm_ksym_ref(buf, "__kstrtabns_", name);
+ buf_printf(buf, "\t.previous\n");
+
+ add_asm_kflagstab(buf, sym);
+}
+
+/* The equivalent of SYMBOL_CRC(). */
+static void add_asm_crc(struct buffer *buf, const struct symbol *sym)
+{
+ buf_printf(buf, "\t.section \"___kcrctab+%s\",\"a\"\n", sym->name);
+ buf_printf(buf, "\t.balign 4\n");
+ buf_printf(buf, "__crc_%s:\n", sym->name);
+ buf_printf(buf, "\t.long 0x%08x\n", sym->crc);
+ buf_printf(buf, "\t.previous\n");
+}
+
+static bool export_is_kept(const struct symbol *sym)
+{
+ return !trim_unused_exports || sym->used;
+}
+
+/* Record the CRCs of the exported symbols. */
+static void add_asm_crcs(struct buffer *buf, struct module *mod)
+{
+ struct symbol *sym;
+
+ list_for_each_entry(sym, &mod->exported_symbols, list) {
+ if (!export_is_kept(sym))
+ continue;
+
+ if (!sym->crc_valid)
+ mod_warn(mod, "EXPORT symbol '%s' version generation failed, symbol will not be versioned.\n"
+ "Is '%s' prototyped in <asm/asm-prototypes.h>?\n",
+ sym->name, sym->name);
+ add_asm_crc(buf, sym);
+ }
+}
+
+static void add_asm_exported_symbols(struct buffer *buf, struct module *mod)
+{
+ struct symbol *sym;
+
+ list_for_each_entry(sym, &mod->exported_symbols, list) {
+ if (export_is_kept(sym))
+ add_asm_ksymtab(buf, sym);
+ }
+
+ if (modversions)
+ add_asm_crcs(buf, mod);
+}
+
+/* Zero fill up to the offset. */
+static void asm_skip_to(struct buffer *buf, unsigned int *pos,
+ unsigned int offset)
+{
+ if (offset < *pos)
+ fatal("__this_module field at offset %u overlaps the previous one\n",
+ offset);
+ if (offset > *pos)
+ buf_printf(buf, "\t.skip %u\n", offset - *pos);
+
+ *pos = offset;
+}
+
+/* A non-zero field of __this_module: its offset and the name or symbol in it. */
+struct this_module_field {
+ unsigned int offset;
+ bool is_name;
+ const char *value;
+};
+
+#define THIS_MODULE_MAX_FIELDS 5
+
+static int compare_field_offsets(const void *ptr_a, const void *ptr_b)
+{
+ const struct this_module_field *field_a = ptr_a, *field_b = ptr_b;
+
+ if (field_a->offset != field_b->offset)
+ return field_a->offset < field_b->offset ? -1 : 1;
+
+ return 0;
+}
+
+/*
+ * The fields of __this_module which are not zero, in offset order. The order
+ * is only known from module-offsets.h, CONFIG_RANDSTRUCT shuffles struct module.
+ */
+static unsigned int get_this_module_fields(const struct module *mod,
+ const char *modname,
+ struct this_module_field *fields)
+{
+ unsigned int nr_fields = 0;
+
+ fields[nr_fields++] = (struct this_module_field)
+ { MOD_OFF_module_name, true, modname };
+ if (mod->has_init)
+ fields[nr_fields++] = (struct this_module_field)
+ { MOD_OFF_module_init, false, MOD_FUNC_PREFIX "init_module" };
+#ifdef MOD_OFF_module_exit
+ if (mod->has_cleanup)
+ fields[nr_fields++] = (struct this_module_field)
+ { MOD_OFF_module_exit, false, MOD_FUNC_PREFIX "cleanup_module" };
+#endif
+#ifdef MOD_OFF_module_arch_fixup_start
+ fields[nr_fields++] = (struct this_module_field)
+ { MOD_OFF_module_arch_fixup_start, false, "__start_fixup" };
+ fields[nr_fields++] = (struct this_module_field)
+ { MOD_OFF_module_arch_fixup_end, false, "__stop_fixup" };
+#endif
+ qsort(fields, nr_fields, sizeof(*fields), compare_field_offsets);
+
+ return nr_fields;
+}
+
+/* Emit one field of __this_module, returning its size. */
+static unsigned int add_asm_this_module_field(struct buffer *buf,
+ const struct this_module_field *field)
+{
+ if (field->is_name) {
+ buf_printf(buf, "\t.ascii \"%s\"\n", field->value);
+ return strlen(field->value);
+ }
+
+ buf_printf(buf, "\t" MOD_PTR_DIRECTIVE " %s\n", field->value);
+ return MOD_SIZEOF_LONG;
+}
+
+/*
+ * The equivalent of:
+ *
+ * __visible struct module __this_module __section(".gnu.linkonce.this_module")
+ * = { .name = KBUILD_MODNAME, .init = init_module, .exit = cleanup_module,
+ * .arch = MODULE_ARCH_INIT };
+ *
+ * Everything not listed is zero, MODULE_ARCH_INIT included, except on m68k.
+ */
+static void add_asm_this_module(struct buffer *buf, const struct module *mod,
+ const char *modname)
+{
+ struct this_module_field fields[THIS_MODULE_MAX_FIELDS];
+ const unsigned int nr_fields = get_this_module_fields(mod, modname, fields);
+ unsigned int pos = 0, i;
+
+ buf_printf(buf, "\n\t.section .gnu.linkonce.this_module,\"aw\",%%progbits\n");
+ buf_printf(buf, "\t.balign %d\n", MOD_ALIGNOF_struct_module);
+ buf_printf(buf, "\t.globl __this_module\n");
+ buf_printf(buf, "\t.type __this_module, %%object\n");
+ buf_printf(buf, "\t.size __this_module, %d\n", MOD_SIZEOF_struct_module);
+ buf_printf(buf, "__this_module:\n");
+
+ for (i = 0; i < nr_fields; i++) {
+ asm_skip_to(buf, &pos, fields[i].offset);
+ pos += add_asm_this_module_field(buf, &fields[i]);
+ }
+
+ asm_skip_to(buf, &pos, MOD_SIZEOF_struct_module);
+}
+
+/*
+ * An unresolved symbol without a module is not versioned; one without a CRC
+ * cannot be, so warn about it.
+ */
+static bool skip_unversioned(struct module *mod, const struct symbol *sym)
+{
+ if (!sym->module)
+ return true;
+ if (sym->crc_valid)
+ return false;
+
+ mod_warn(mod, "symbol '%s' has no CRC!\n", sym->name);
+ return true;
+}
+
+/* One struct modversion_info: the CRC, then the name padded to the end. */
+static void add_asm_version(struct buffer *buf, const struct symbol *sym)
+{
+ buf_printf(buf, "\t" MOD_PTR_DIRECTIVE " 0x%08x\n", sym->crc);
+ buf_printf(buf, "\t.ascii \"%s\"\n", sym->name);
+ buf_printf(buf, "\t.skip %zu\n", MOD_SIZEOF_struct_modversion_info -
+ MOD_OFF_modversion_info_name - strlen(sym->name));
+}
+
+/*
+ * The equivalent of:
+ *
+ * static const struct modversion_info ____versions[]
+ * __used __section("__versions") = { { crc, "name" }, ... };
+ *
+ * for unresolved symbols.
+ */
+static void add_asm_versions(struct buffer *buf, struct module *mod)
+{
+ struct symbol *sym;
+
+ if (!basic_modversions)
+ return;
+
+ buf_printf(buf, "\n\t.section __versions,\"a\",%%progbits\n");
+ buf_printf(buf, "\t.balign %d\n", MOD_ALIGNOF_struct_modversion_info);
+ list_for_each_entry(sym, &mod->unresolved_symbols, list) {
+ if (skip_unversioned(mod, sym))
+ continue;
+
+ if (strlen(sym->name) >= MOD_NAME_LEN) {
+ /* Only the extended table can hold it. */
+ if (extended_modversions)
+ continue;
+
+ mod_error(mod, "too long symbol '%s'\n", sym->name);
+ break;
+ }
+
+ add_asm_version(buf, sym);
+ }
+}
+
+static void add_asm_version_ext_crcs(struct buffer *buf, struct module *mod)
+{
+ struct symbol *sym;
+
+ buf_printf(buf, "\n\t.section __version_ext_crcs,\"a\",%%progbits\n");
+ buf_printf(buf, "\t.balign 4\n");
+ list_for_each_entry(sym, &mod->unresolved_symbols, list) {
+ if (skip_unversioned(mod, sym))
+ continue;
+
+ buf_printf(buf, "\t.long 0x%08x\n", sym->crc);
+ }
+}
+
+/*
+ * A symbol without a CRC was warned about with the CRCs, and is skipped here
+ * too so that the names line up with them.
+ */
+static void add_asm_version_ext_names(struct buffer *buf, struct module *mod)
+{
+ struct symbol *sym;
+
+ buf_printf(buf, "\t.section __version_ext_names,\"a\",%%progbits\n");
+ list_for_each_entry(sym, &mod->unresolved_symbols, list) {
+ if (!sym->module || !sym->crc_valid)
+ continue;
+
+ buf_asciz(buf, sym->name);
+ }
+ /* The terminator of the string literal this used to be. */
+ buf_printf(buf, "\t.byte 0\n");
+}
+
+/*
+ * The equivalent of:
+ * static const u32 ____version_ext_crcs[] __section("__version_ext_crcs") = { crc, ... };
+ * static const char ____version_ext_names[] __section("__version_ext_names") = "name\0" ...;
+ *
+ * for unresolved symbols.
+ */
+static void add_asm_extended_versions(struct buffer *buf, struct module *mod)
+{
+ if (!extended_modversions)
+ return;
+
+ add_asm_version_ext_crcs(buf, mod);
+ add_asm_version_ext_names(buf, mod);
+}
+
+/* Clear ->seen of the modules that own symbols this one needs. */
+static void clear_seen_dependencies(struct module *mod)
+{
+ struct symbol *sym;
+
+ list_for_each_entry(sym, &mod->unresolved_symbols, list) {
+ if (sym->module)
+ sym->module->seen = sym->module->is_vmlinux;
+ }
+}
+
+/* The modules this one depends on, each once, comma separated. */
+static void collect_dependencies(struct module *mod, struct buffer *deps)
+{
+ struct symbol *sym;
+ bool first = true;
+
+ clear_seen_dependencies(mod);
+
+ list_for_each_entry(sym, &mod->unresolved_symbols, list) {
+ struct module *owner = sym->module;
+
+ if (!owner || owner->seen)
+ continue;
+
+ owner->seen = true;
+ buf_printf(deps, "%s%s", first ? "" : ",",
+ get_basename(owner->name));
+ first = false;
+ }
+ buf_write(deps, "", 1);
+}
+
+static void add_asm_depends(struct buffer *buf, struct module *mod)
+{
+ struct buffer deps = { };
+
+ collect_dependencies(mod, &deps);
+ buf_printf(buf, "\n");
+ add_asm_modinfo(buf, "depends", deps.p);
+ free(deps.p);
+}
+
+/*
+ * KBUILD_MODNAME: the basename of the module with '-' and ',' replaced by
+ * '_' (see name-fix in scripts/Makefile.lib).
+ */
+static char *get_kbuild_modname(const struct module *mod)
+{
+ char *name = xstrdup(get_basename(mod->name));
+ char *curr;
+
+ for (curr = name; *curr; curr++) {
+ if (*curr == '-' || *curr == ',')
+ *curr = '_';
+ }
+
+ return name;
+}
+
+/* The module's name, its descriptor, and where it comes from. */
+static void add_asm_header(struct buffer *buf, const struct module *mod,
+ const char *modname)
+{
+ buf_printf(buf, "/* Generated by modpost, see scripts/Makefile.modfinal */\n\n");
+
+ add_asm_modinfo(buf, "name", modname);
+ add_asm_this_module(buf, mod, modname);
+ buf_printf(buf, "\n");
+
+ if (!external_module)
+ add_asm_modinfo(buf, "intree", "Y");
+ if (strstarts(mod->name, "drivers/staging"))
+ add_asm_modinfo(buf, "staging", "Y");
+ if (strstarts(mod->name, "tools/testing"))
+ add_asm_modinfo(buf, "test", "Y");
+}
+
+static void add_asm_aliases(struct buffer *buf, struct module *mod)
+{
+ struct module_alias *alias, *next;
+
+ buf_printf(buf, "\n");
list_for_each_entry_safe(alias, next, &mod->aliases, node) {
- buf_printf(&buf, "MODULE_ALIAS(\"%s\");\n", alias->str);
+ add_asm_modinfo(buf, "alias", alias->str);
list_del(&alias->node);
free(alias);
}
+}
- add_srcversion(&buf, mod);
+static void add_asm_srcversion(struct buffer *buf, const struct module *mod)
+{
+ if (!mod->srcversion[0])
+ return;
- ret = snprintf(fname, sizeof(fname), "%s.mod.c", mod->name);
+ buf_printf(buf, "\n");
+ add_asm_modinfo(buf, "srcversion", mod->srcversion);
+}
+
+static void write_mod_S_file(struct module *mod)
+{
+ struct buffer buf = { };
+ char fname[PATH_MAX];
+ char *modname = get_kbuild_modname(mod);
+ int ret;
+
+ add_asm_header(&buf, mod, modname);
+ buf_printf(&buf, "\n");
+ add_asm_exported_symbols(&buf, mod);
+ add_asm_versions(&buf, mod);
+ add_asm_extended_versions(&buf, mod);
+ add_asm_depends(&buf, mod);
+ add_asm_aliases(&buf, mod);
+ add_asm_srcversion(&buf, mod);
+ buf_printf(&buf, "\n\t.section .note.GNU-stack,\"\",%%progbits\n");
+
+ ret = snprintf(fname, sizeof(fname), "%s.mod.S", mod->name);
if (ret >= sizeof(fname)) {
error("%s: too long path was truncated\n", fname);
goto free;
@@ -2223,6 +2527,7 @@ static void write_mod_c_file(struct module *mod)
write_if_changed(&buf, fname);
free:
+ free(modname);
free(buf.p);
}
@@ -2462,7 +2767,7 @@ int main(int argc, char **argv)
if (mod->is_vmlinux)
write_vmlinux_export_c_file(mod);
else
- write_mod_c_file(mod);
+ write_mod_S_file(mod);
}
if (missing_namespace_deps)
diff --git a/scripts/mod/module-offsets.c b/scripts/mod/module-offsets.c
new file mode 100644
index 000000000000..a336dd47aa33
--- /dev/null
+++ b/scripts/mod/module-offsets.c
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Layout of the structures modpost emits into *.mod.S, extracted from the
+ * target headers as devicetable-offsets.c does for the device tables.
+ */
+#define COMPILE_OFFSETS
+#include <linux/kbuild.h>
+#include <linux/module.h>
+
+int main(void)
+{
+ DEFINE(MOD_SIZEOF_LONG, sizeof(long));
+ DEFINE(MOD_PREL32_RELOCATIONS, IS_ENABLED(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS));
+ DEFINE(MOD_FUNC_PLABEL, IS_ENABLED(CONFIG_PARISC) && IS_ENABLED(CONFIG_64BIT));
+
+ DEFINE(MOD_SIZEOF_struct_module, sizeof(struct module));
+ DEFINE(MOD_ALIGNOF_struct_module, __alignof__(struct module));
+ OFFSET(MOD_OFF_module_name, module, name);
+ OFFSET(MOD_OFF_module_init, module, init);
+#ifdef CONFIG_MODULE_UNLOAD
+ OFFSET(MOD_OFF_module_exit, module, exit);
+#endif
+#if defined(CONFIG_M68K) && defined(CONFIG_MMU)
+ /* MODULE_ARCH_INIT: the only architecture where it is not all zeroes. */
+ OFFSET(MOD_OFF_module_arch_fixup_start, module, arch.fixup_start);
+ OFFSET(MOD_OFF_module_arch_fixup_end, module, arch.fixup_end);
+#endif
+ DEFINE(MOD_NAME_LEN, MODULE_NAME_LEN);
+
+ DEFINE(MOD_SIZEOF_struct_modversion_info, sizeof(struct modversion_info));
+ DEFINE(MOD_ALIGNOF_struct_modversion_info, __alignof__(struct modversion_info));
+ OFFSET(MOD_OFF_modversion_info_name, modversion_info, name);
+
+ return 0;
+}
diff --git a/scripts/tags.sh b/scripts/tags.sh
index 41e38df96984..c33d0f58a9d4 100755
--- a/scripts/tags.sh
+++ b/scripts/tags.sh
@@ -14,8 +14,9 @@ fi
# RCS_FIND_IGNORE has escaped ()s -- remove them.
ignore="$(echo "$RCS_FIND_IGNORE" | sed 's|\\||g' )"
-# tags and cscope files should also ignore MODVERSION *.mod.c files
-ignore="$ignore ( -name *.mod.c ) -prune -o"
+# tags and cscope files should also ignore the modpost-generated *.mod.S files
+# and any *.mod.c left behind from before they were assembly
+ignore="$ignore ( -name *.mod.c -o -name *.mod.S ) -prune -o"
# ignore arbitrary directories
if [ -n "${IGNORE_DIRS}" ]; then
--
2.55.0
next prev parent reply other threads:[~2026-09-14 9:24 UTC|newest]
Thread overview: 44+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-14 9:21 [PATCH v2 00/21] kbuild: significantly speed up kernel builds Lorenzo Stoakes (ARM)
2026-09-14 9:22 ` [PATCH v2 01/21] kbuild: do not allocate .modinfo in vmlinux Lorenzo Stoakes (ARM)
2026-09-14 9:22 ` [PATCH v2 02/21] kallsyms: index symbols by token to speed up table compression Lorenzo Stoakes (ARM)
2026-09-14 9:22 ` [PATCH v2 03/21] kallsyms: output binary data to speed output and kallsyms assembly Lorenzo Stoakes (ARM)
2026-09-14 20:16 ` Markus Elfring
2026-09-14 21:44 ` David Laight
2026-09-14 9:22 ` [PATCH v2 04/21] kbuild: do not sort nm output where the order is irrelevant Lorenzo Stoakes (ARM)
2026-09-14 9:22 ` [PATCH v2 05/21] kbuild: only emit vmlinux relocations when required Lorenzo Stoakes (ARM)
2026-09-14 9:22 ` [PATCH v2 06/21] elf-parse: add section flags, symbol binding and a read-only mapping Lorenzo Stoakes (ARM)
2026-09-14 9:22 ` [PATCH v2 07/21] kallsyms: reimplement mksysmap in C Lorenzo Stoakes (ARM)
2026-09-14 16:33 ` Markus Elfring
2026-09-14 16:54 ` Markus Elfring
2026-09-14 17:01 ` Markus Elfring
2026-09-14 9:22 ` [PATCH v2 08/21] kbuild: cache list, composite object state per object Lorenzo Stoakes (ARM)
2026-09-14 9:22 ` [PATCH v2 09/21] kbuild: implement and use depcheck to check dependency timestamps Lorenzo Stoakes (ARM)
2026-09-14 9:22 ` [PATCH v2 10/21] kbuild: move the toolchain checks into init/Kconfig.toolchain Lorenzo Stoakes (ARM)
2026-09-14 9:22 ` [PATCH v2 11/21] kbuild: avoid re-running compiler and linker probes Lorenzo Stoakes (ARM)
2026-09-14 15:02 ` John Stoffel
2026-09-14 15:24 ` Lorenzo Stoakes (ARM)
2026-09-14 9:22 ` [PATCH v2 12/21] modpost: cache section relocation mismatch state Lorenzo Stoakes (ARM)
2026-09-14 9:22 ` Lorenzo Stoakes (ARM) [this message]
2026-09-14 9:22 ` [PATCH v2 14/21] kbuild: batch module finalisation Lorenzo Stoakes (ARM)
2026-09-14 18:00 ` Kees Cook
2026-09-14 9:22 ` [PATCH v2 15/21] objtool: cache relocations, do less work Lorenzo Stoakes (ARM)
2026-09-14 19:44 ` Josh Poimboeuf
2026-09-14 20:06 ` Linus Torvalds
2026-09-14 22:23 ` Josh Poimboeuf
2026-09-14 22:30 ` Linus Torvalds
2026-09-14 9:22 ` [PATCH v2 16/21] objtool: size the instruction hash to the text Lorenzo Stoakes (ARM)
2026-09-14 9:22 ` [PATCH v2 17/21] objtool: decode instructions and resolve branch targets in parallel Lorenzo Stoakes (ARM)
2026-09-14 18:20 ` Kees Cook
2026-09-14 9:22 ` [PATCH v2 18/21] kbuild: rust: optionally parallelise rustc front end Lorenzo Stoakes (ARM)
2026-09-14 18:32 ` Kees Cook
2026-09-14 9:22 ` [PATCH v2 19/21] rust: make exports.o depend on the headers generated for it Lorenzo Stoakes (ARM)
2026-09-14 9:22 ` [PATCH v2 20/21] kbuild: build rust crates in parallel with the rest of the build Lorenzo Stoakes (ARM)
2026-09-14 18:37 ` Kees Cook
2026-09-14 9:22 ` [PATCH v2 21/21] kbuild: use pigz for gzip compression if available Lorenzo Stoakes (ARM)
2026-09-14 16:39 ` Kees Cook
2026-09-14 16:49 ` H. Peter Anvin
2026-09-14 17:50 ` Kees Cook
2026-09-14 15:41 ` [PATCH v2 00/21] kbuild: significantly speed up kernel builds Kees Cook
2026-09-14 15:53 ` Linus Torvalds
2026-09-14 18:25 ` Lorenzo Stoakes (ARM)
2026-09-14 18:43 ` Kees Cook
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260914-build-speedup-v2-13-39817ec5db23@kernel.org \
--to=ljs@kernel.org \
--cc=a.hindborg@kernel.org \
--cc=acourbot@nvidia.com \
--cc=alex@ghiti.fr \
--cc=aliceryhl@google.com \
--cc=aou@eecs.berkeley.edu \
--cc=ardb@kernel.org \
--cc=arnd@arndb.de \
--cc=axboe@kernel.dk \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun@kernel.org \
--cc=bp@alien8.de \
--cc=catalin.marinas@arm.com \
--cc=corbet@lwn.net \
--cc=dakr@kernel.org \
--cc=daniel.almeida@collabora.com \
--cc=dave.hansen@linux.intel.com \
--cc=gary@garyguo.net \
--cc=gustavoars@kernel.org \
--cc=hpa@zytor.com \
--cc=ilias.apalodimas@linaro.org \
--cc=jpoimboe@kernel.org \
--cc=justinstitt@google.com \
--cc=kees@kernel.org \
--cc=legion@kernel.org \
--cc=linux-arch@vger.kernel.org \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-efi@vger.kernel.org \
--cc=linux-hardening@vger.kernel.org \
--cc=linux-kbuild@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-riscv@lists.infradead.org \
--cc=llvm@lists.linux.dev \
--cc=lossin@kernel.org \
--cc=mark.rutland@arm.com \
--cc=masahiroy@kernel.org \
--cc=mingo@redhat.com \
--cc=morbo@google.com \
--cc=nathan@kernel.org \
--cc=ndesaulniers@google.com \
--cc=nsc@kernel.org \
--cc=ojeda@kernel.org \
--cc=palmer@dabbelt.com \
--cc=peterz@infradead.org \
--cc=pjw@kernel.org \
--cc=rdunlap@infradead.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=tamird@kernel.org \
--cc=tglx@kernel.org \
--cc=tmgross@umich.edu \
--cc=torvalds@linux-foundation.org \
--cc=will@kernel.org \
--cc=work@onurozkan.dev \
--cc=x86@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®