diff options
author | Masahiro Yamada <masahiroy@kernel.org> | 2023-06-11 18:50:57 +0300 |
---|---|---|
committer | Masahiro Yamada <masahiroy@kernel.org> | 2023-06-22 15:21:06 +0300 |
commit | 5e9e95cc9148b82074a5eae283e63bce3f1aacfe (patch) | |
tree | 72072798944c4a0ffcf2f294b8c264cbd7f60b05 /scripts/mod/modpost.c | |
parent | 700c48b439921b67715e25380e0f67e6e490d7b8 (diff) | |
download | linux-5e9e95cc9148b82074a5eae283e63bce3f1aacfe.tar.xz |
kbuild: implement CONFIG_TRIM_UNUSED_KSYMS without recursion
When CONFIG_TRIM_UNUSED_KSYMS is enabled, Kbuild recursively traverses
the directory tree to determine which EXPORT_SYMBOL to trim. If an
EXPORT_SYMBOL turns out to be unused by anyone, Kbuild begins the
second traverse, where some source files are recompiled with their
EXPORT_SYMBOL() tuned into a no-op.
Linus stated negative opinions about this slowness in commits:
- 5cf0fd591f2e ("Kbuild: disable TRIM_UNUSED_KSYMS option")
- a555bdd0c58c ("Kbuild: enable TRIM_UNUSED_KSYMS again, with some guarding")
We can do this better now. The final data structures of EXPORT_SYMBOL
are generated by the modpost stage, so modpost can selectively emit
KSYMTAB entries that are really used by modules.
Commit f73edc8951b2 ("kbuild: unify two modpost invocations") is another
ground-work to do this in a one-pass algorithm. With the list of modules,
modpost sets sym->used if it is used by a module. modpost emits KSYMTAB
only for symbols with sym->used==true.
BTW, Nicolas explained why the trimming was implemented with recursion:
https://lore.kernel.org/all/2o2rpn97-79nq-p7s2-nq5-8p83391473r@syhkavp.arg/
Actually, we never achieved that level of optimization where the chain
reaction of trimming comes into play because:
- CONFIG_LTO_CLANG cannot remove any unused symbols
- CONFIG_LD_DEAD_CODE_DATA_ELIMINATION is enabled only for vmlinux,
but not modules
If deeper trimming is required, we need to revisit this, but I guess
that is unlikely to happen.
Signed-off-by: Masahiro Yamada <masahiroy@kernel.org>
Diffstat (limited to 'scripts/mod/modpost.c')
-rw-r--r-- | scripts/mod/modpost.c | 57 |
1 files changed, 52 insertions, 5 deletions
diff --git a/scripts/mod/modpost.c b/scripts/mod/modpost.c index a7c979b0ea21..3d9f3e2b2a2d 100644 --- a/scripts/mod/modpost.c +++ b/scripts/mod/modpost.c @@ -35,6 +35,9 @@ static bool warn_unresolved; static int sec_mismatch_count; static bool sec_mismatch_warn_only = true; +/* Trim EXPORT_SYMBOLs that are unused by in-tree modules */ +static bool trim_unused_exports; + /* ignore missing files */ static bool ignore_missing_files; /* If set to 1, only warn (instead of error) about missing ns imports */ @@ -219,6 +222,7 @@ struct symbol { bool weak; bool is_func; bool is_gpl_only; /* exported by EXPORT_SYMBOL_GPL */ + bool used; /* there exists a user of this symbol */ char name[]; }; @@ -1826,6 +1830,7 @@ static void check_exports(struct module *mod) continue; } + exp->used = true; s->module = exp->module; s->crc_valid = exp->crc_valid; s->crc = exp->crc; @@ -1849,6 +1854,23 @@ static void check_exports(struct module *mod) } } +static void handle_white_list_exports(const char *white_list) +{ + char *buf, *p, *name; + + buf = read_text_file(white_list); + p = buf; + + while ((name = strsep(&p, "\n"))) { + struct symbol *sym = find_symbol(name); + + if (sym) + sym->used = true; + } + + free(buf); +} + static void check_modname_len(struct module *mod) { const char *mod_name; @@ -1919,10 +1941,14 @@ static void add_exported_symbols(struct buffer *buf, struct module *mod) /* generate struct for exported symbols */ buf_printf(buf, "\n"); - list_for_each_entry(sym, &mod->exported_symbols, list) + list_for_each_entry(sym, &mod->exported_symbols, list) { + if (trim_unused_exports && !sym->used) + continue; + buf_printf(buf, "KSYMTAB_%s(%s, \"%s\", \"%s\");\n", sym->is_func ? "FUNC" : "DATA", sym->name, sym->is_gpl_only ? "_gpl" : "", sym->namespace); + } if (!modversions) return; @@ -1930,6 +1956,9 @@ static void add_exported_symbols(struct buffer *buf, struct module *mod) /* record CRCs for exported symbols */ buf_printf(buf, "\n"); list_for_each_entry(sym, &mod->exported_symbols, list) { + if (trim_unused_exports && !sym->used) + continue; + if (!sym->crc_valid) warn("EXPORT symbol \"%s\" [%s%s] version generation failed, symbol will not be versioned.\n" "Is \"%s\" prototyped in <asm/asm-prototypes.h>?\n", @@ -2093,9 +2122,6 @@ static void write_mod_c_file(struct module *mod) char fname[PATH_MAX]; int ret; - check_modname_len(mod); - check_exports(mod); - add_header(&buf, mod); add_exported_symbols(&buf, mod); add_versions(&buf, mod); @@ -2187,6 +2213,9 @@ static void write_dump(const char *fname) if (mod->from_dump) continue; list_for_each_entry(sym, &mod->exported_symbols, list) { + if (trim_unused_exports && !sym->used) + continue; + buf_printf(&buf, "0x%08x\t%s\t%s\tEXPORT_SYMBOL%s\t%s\n", sym->crc, sym->name, mod->name, sym->is_gpl_only ? "_GPL" : "", @@ -2229,12 +2258,13 @@ int main(int argc, char **argv) { struct module *mod; char *missing_namespace_deps = NULL; + char *unused_exports_white_list = NULL; char *dump_write = NULL, *files_source = NULL; int opt; LIST_HEAD(dump_lists); struct dump_list *dl, *dl2; - while ((opt = getopt(argc, argv, "ei:mnT:o:aWwENd:")) != -1) { + while ((opt = getopt(argc, argv, "ei:mnT:to:au:WwENd:")) != -1) { switch (opt) { case 'e': external_module = true; @@ -2259,6 +2289,12 @@ int main(int argc, char **argv) case 'T': files_source = optarg; break; + case 't': + trim_unused_exports = true; + break; + case 'u': + unused_exports_white_list = optarg; + break; case 'W': extra_warn = true; break; @@ -2292,6 +2328,17 @@ int main(int argc, char **argv) read_symbols_from_files(files_source); list_for_each_entry(mod, &modules, list) { + if (mod->from_dump || mod->is_vmlinux) + continue; + + check_modname_len(mod); + check_exports(mod); + } + + if (unused_exports_white_list) + handle_white_list_exports(unused_exports_white_list); + + list_for_each_entry(mod, &modules, list) { if (mod->from_dump) continue; |