]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
kbuild: keep symbols for symbol_get() even with CONFIG_TRIM_UNUSED_KSYMS
authorMasahiro Yamada <masahiroy@kernel.org>
Sat, 1 Feb 2025 18:51:41 +0000 (03:51 +0900)
committerMasahiro Yamada <masahiroy@kernel.org>
Wed, 5 Feb 2025 16:08:58 +0000 (01:08 +0900)
Linus observed that the symbol_request(utf8_data_table) call fails when
CONFIG_UNICODE=y and CONFIG_TRIM_UNUSED_KSYMS=y.

symbol_get() relies on the symbol data being present in the ksymtab for
symbol lookups. However, EXPORT_SYMBOL_GPL(utf8_data_table) is dropped
due to CONFIG_TRIM_UNUSED_KSYMS, as no module references it in this case.

Probably, this has been broken since commit dbacb0ef670d ("kconfig option
for TRIM_UNUSED_KSYMS").

This commit addresses the issue by leveraging modpost. Symbol names
passed to symbol_get() are recorded in the special .no_trim_symbol
section, which is then parsed by modpost to forcibly keep such symbols.
The .no_trim_symbol section is discarded by the linker scripts, so there
is no impact on the size of the final vmlinux or modules.

This commit cannot resolve the issue for direct calls to __symbol_get()
because the symbol name is not known at compile-time.

Although symbol_get() may eventually be deprecated, this workaround
should be good enough meanwhile.

Reported-by: Linus Torvalds <torvalds@linux-foundation.org>
Suggested-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Masahiro Yamada <masahiroy@kernel.org>
include/asm-generic/vmlinux.lds.h
include/linux/module.h
scripts/mod/modpost.c
scripts/mod/modpost.h
scripts/module.lds.S

index 54504013c74915c2ed923fb3afde024a69cdae6b..02a4adb4a99992cd4c47c66123df23847fb12bc9 100644 (file)
@@ -1038,6 +1038,7 @@ defined(CONFIG_AUTOFDO_CLANG) || defined(CONFIG_PROPELLER_CLANG)
        *(.discard)                                                     \
        *(.discard.*)                                                   \
        *(.export_symbol)                                               \
+       *(.no_trim_symbol)                                              \
        *(.modinfo)                                                     \
        /* ld.bfd warns about .gnu.version* even when not emitted */    \
        *(.gnu.version*)                                                \
index 23792d5d7b74bdbf7efaecd627e138e7746e2cfe..30e5b19bafa983e70e2a0509e0ccc3d7f58a7d6f 100644 (file)
@@ -306,7 +306,10 @@ extern int modules_disabled; /* for sysctl */
 /* Get/put a kernel symbol (calls must be symmetric) */
 void *__symbol_get(const char *symbol);
 void *__symbol_get_gpl(const char *symbol);
-#define symbol_get(x) ((typeof(&x))(__symbol_get(__stringify(x))))
+#define symbol_get(x)  ({ \
+       static const char __notrim[] \
+               __used __section(".no_trim_symbol") = __stringify(x); \
+       (typeof(&x))(__symbol_get(__stringify(x))); })
 
 /* modules using other modules: kdb wants to see this. */
 struct module_use {
index e18ae7dc8140a6d3d3bd26dd67cb1db5546b003d..36b28987a2f07584e1b2337d4e9ca3a1ae1ce635 100644 (file)
@@ -507,6 +507,9 @@ static int parse_elf(struct elf_info *info, const char *filename)
                        info->modinfo_len = sechdrs[i].sh_size;
                } else if (!strcmp(secname, ".export_symbol")) {
                        info->export_symbol_secndx = i;
+               } else if (!strcmp(secname, ".no_trim_symbol")) {
+                       info->no_trim_symbol = (void *)hdr + sechdrs[i].sh_offset;
+                       info->no_trim_symbol_len = sechdrs[i].sh_size;
                }
 
                if (sechdrs[i].sh_type == SHT_SYMTAB) {
@@ -1566,6 +1569,14 @@ static void read_symbols(const char *modname)
        /* strip trailing .o */
        mod = new_module(modname, strlen(modname) - strlen(".o"));
 
+       /* save .no_trim_symbol section for later use */
+       if (info.no_trim_symbol_len) {
+               mod->no_trim_symbol = xmalloc(info.no_trim_symbol_len);
+               memcpy(mod->no_trim_symbol, info.no_trim_symbol,
+                      info.no_trim_symbol_len);
+               mod->no_trim_symbol_len = info.no_trim_symbol_len;
+       }
+
        if (!mod->is_vmlinux) {
                license = get_modinfo(&info, "license");
                if (!license)
@@ -1728,6 +1739,28 @@ static void handle_white_list_exports(const char *white_list)
        free(buf);
 }
 
+/*
+ * Keep symbols recorded in the .no_trim_symbol section. This is necessary to
+ * prevent CONFIG_TRIM_UNUSED_KSYMS from dropping EXPORT_SYMBOL because
+ * symbol_get() relies on the symbol being present in the ksymtab for lookups.
+ */
+static void keep_no_trim_symbols(struct module *mod)
+{
+       unsigned long size = mod->no_trim_symbol_len;
+
+       for (char *s = mod->no_trim_symbol; s; s = next_string(s , &size)) {
+               struct symbol *sym;
+
+               /*
+                * If find_symbol() returns NULL, this symbol is not provided
+                * by any module, and symbol_get() will fail.
+                */
+               sym = find_symbol(s);
+               if (sym)
+                       sym->used = true;
+       }
+}
+
 static void check_modname_len(struct module *mod)
 {
        const char *mod_name;
@@ -2254,6 +2287,8 @@ int main(int argc, char **argv)
                read_symbols_from_files(files_source);
 
        list_for_each_entry(mod, &modules, list) {
+               keep_no_trim_symbols(mod);
+
                if (mod->dump_file || mod->is_vmlinux)
                        continue;
 
index ffd0a52a606efab8297f9df88047e5d29eef61a3..59366f456b76590b44b62742a6052f1792293d6f 100644 (file)
@@ -111,6 +111,8 @@ struct module_alias {
  *
  * @dump_file: path to the .symvers file if loaded from a file
  * @aliases: list head for module_aliases
+ * @no_trim_symbol: .no_trim_symbol section data
+ * @no_trim_symbol_len: length of the .no_trim_symbol section
  */
 struct module {
        struct list_head list;
@@ -128,6 +130,8 @@ struct module {
        // Actual imported namespaces
        struct list_head imported_namespaces;
        struct list_head aliases;
+       char *no_trim_symbol;
+       unsigned int no_trim_symbol_len;
        char name[];
 };
 
@@ -141,6 +145,8 @@ struct elf_info {
        char         *strtab;
        char         *modinfo;
        unsigned int modinfo_len;
+       char         *no_trim_symbol;
+       unsigned int no_trim_symbol_len;
 
        /* support for 32bit section numbers */
 
index c2f80f9141d407a94eac4aae1560c17c129a84ee..450f1088d5fd3643c9763abe4282bc62b45bd46b 100644 (file)
@@ -16,6 +16,7 @@ SECTIONS {
                *(.discard)
                *(.discard.*)
                *(.export_symbol)
+               *(.no_trim_symbol)
        }
 
        __ksymtab               0 : ALIGN(8) { *(SORT(___ksymtab+*)) }