]> www.infradead.org Git - users/jedix/linux-maple.git/commitdiff
kallsyms: add /proc/kallmodsyms
authorNick Alcock <nick.alcock@oracle.com>
Fri, 11 May 2012 20:21:33 +0000 (21:21 +0100)
committerNick Alcock <nick.alcock@oracle.com>
Mon, 29 Jun 2015 21:40:24 +0000 (22:40 +0100)
This procfile (controlled by CONFIG_KALLSYMS), introduces a new
/proc/kallmodsyms whose contents are the same as /proc/kallsyms, except that the
module name is provided even for built-in modules, as long as they could
potentially have been built as separate kernel modules.  DTrace will use it in
future to allow users to use the same module names in their D scripts regardless
of whether the modules are built-in or not in the kernel they happen to be
running.

This is done by scanning the object files named in the builtin.modules file
using libelf, extracting their function symbols, pruning out any symbols which
appear in more than one builtin module (since these are probably out-of-line
copies of inline functions in common kernel header files), then emitting the
names of all the modules in a new kallsyms_modules symbol and pointing at the
appropriate modules in a kallsyms_symbol_modules symbol which maps 1:1 to the
contents of kallsyms_addresses (thus, one kallsyms_symbol entry per kernel
symbol in /proc/kallsyms).  Always-built-in functions have a corresponding
kallsyms_symbol_modules index of zero.

This is not terribly space-efficient: the kallsyms_symbol_modules symbol is
quite large (~250K, mostly zeroes, four bytes per kernel symbol), but reducing
this requires some other way to signal that a symbol is not present in a module.
It is much smaller than even the compressed representation of the symbol names.

Possible future enhancements, low priority:
 - use a home-cooked hashtable rather than glib (pointless unless dwarf2ctf is
   similarly rejigged)
 - use nm rather than elfutils (liable to be quite a lot slower)
 - find a more robust way of detecting inlined functions, not fooled by
   out-of-line copies of inlined functions which happen to be used by
   only one module
 - shrink the kernel symbol space consumption, perhaps by using shorts rather
   than longs for the module offsets (65536Kb of module names should be enough
   for anybody)

Signed-off-by: Nick Alcock <nick.alcock@oracle.com>
Documentation/Changes
include/linux/kallsyms.h
kernel/kallsyms.c
scripts/Makefile
scripts/kallsyms.c

index 646cdaa6e9d13304b7e647ccf158518634122feb..4db80e85cd57251b8179f19bcf210d6cd09aedcc 100644 (file)
@@ -44,6 +44,11 @@ o  grub                   0.93                    # grub --version || grub-insta
 o  mcelog                 0.6                     # mcelog --version
 o  iptables               1.4.2                   # iptables -V
 
+Build-time requirements only:
+
+o  elfutils               0.152                   # eu-readelf --version
+o  pkg-config             0.16                    # pkg-config --version
+o  glib                   2.x                     # pkg-config --exists glib-2.0 && echo present
 
 Kernel compilation
 ==================
@@ -393,3 +398,14 @@ NFS-Utils
 ---------
 o  <http://nfs.sourceforge.net/>
 
+elfutils (build-time only)
+--------
+o  <https://fedorahosted.org/elfutils/>
+
+pkg-config (build-time only)
+----------
+o  <http://www.freedesktop.org/wiki/Software/pkg-config>
+
+glib (build-time only: 2.x required, not 3.x)
+----
+o  <http://www.gtk.org/>
index 4dcdda928f094a5f982378f675b52d8d9c592b3c..8472c3dbace993f9b3ad0b0a90f3f48c27b2c0d7 100644 (file)
@@ -25,6 +25,7 @@ struct kallsym_iter {
        char type;
        char name[KSYM_NAME_LEN];
        char module_name[MODULE_NAME_LEN];
+       int builtin_module;
        int exported;
 };
 
index 7b92a0f0c43de0da3335186a724c9ad6054b2ff8..cf4b3a7e6a06e88ee6fe73ea6f122da1a7f5e9ec 100644 (file)
@@ -49,6 +49,8 @@ __attribute__((weak, section(".rodata")));
 
 extern const u8 kallsyms_token_table[] __weak;
 extern const u16 kallsyms_token_index[] __weak;
+extern const char kallsyms_modules[] __weak;
+extern const u32 kallsyms_symbol_modules[] __weak;
 
 extern const unsigned long kallsyms_markers[] __weak;
 
@@ -447,6 +449,7 @@ EXPORT_SYMBOL(__print_symbol);
 
 static int get_ksymbol_mod(struct kallsym_iter *iter)
 {
+       iter->builtin_module = 0;
        if (module_get_kallsym(iter->pos - kallsyms_num_syms, &iter->value,
                                &iter->type, iter->name, iter->module_name,
                                &iter->exported) < 0)
@@ -458,8 +461,16 @@ static int get_ksymbol_mod(struct kallsym_iter *iter)
 static unsigned long get_ksymbol_core(struct kallsym_iter *iter)
 {
        unsigned off = iter->nameoff;
-
-       iter->module_name[0] = '\0';
+       u32 mod_index = kallsyms_symbol_modules[iter->pos];
+
+       if (mod_index == 0) {
+               iter->module_name[0] = '\0';
+               iter->builtin_module = 0;
+       } else {
+               strcpy(iter->module_name, &kallsyms_modules[mod_index]);
+               iter->builtin_module = 1;
+       }
+       iter->exported = 0;
        iter->value = kallsyms_addresses[iter->pos];
 
        iter->type = kallsyms_get_symbol_type(off);
@@ -517,7 +528,7 @@ static void s_stop(struct seq_file *m, void *p)
 {
 }
 
-static int s_show(struct seq_file *m, void *p)
+static int s_show_internal(struct seq_file *m, void *p, int builtin_modules)
 {
        struct kallsym_iter *iter = m->private;
 
@@ -525,7 +536,9 @@ static int s_show(struct seq_file *m, void *p)
        if (!iter->name[0])
                return 0;
 
-       if (iter->module_name[0]) {
+       if ((iter->builtin_module == 0 && iter->module_name[0]) ||
+           (iter->builtin_module != 0 && iter->module_name[0] &&
+            builtin_modules != 0)) {
                char type;
 
                /*
@@ -536,12 +549,23 @@ static int s_show(struct seq_file *m, void *p)
                                        tolower(iter->type);
                seq_printf(m, "%pK %c %s\t[%s]\n", (void *)iter->value,
                           type, iter->name, iter->module_name);
-       } else
+       } else {
                seq_printf(m, "%pK %c %s\n", (void *)iter->value,
                           iter->type, iter->name);
+       }
        return 0;
 }
 
+static int s_show(struct seq_file *m, void *p)
+{
+       return s_show_internal(m, p, 0);
+}
+
+static int s_mod_show(struct seq_file *m, void *p)
+{
+       return s_show_internal(m, p, 1);
+}
+
 static const struct seq_operations kallsyms_op = {
        .start = s_start,
        .next = s_next,
@@ -549,7 +573,15 @@ static const struct seq_operations kallsyms_op = {
        .show = s_show
 };
 
-static int kallsyms_open(struct inode *inode, struct file *file)
+static const struct seq_operations kallmodsyms_op = {
+       .start = s_start,
+       .next = s_next,
+       .stop = s_stop,
+       .show = s_mod_show
+};
+
+static int kallsyms_open_internal(struct inode *inode, struct file *file,
+       const struct seq_operations *ops)
 {
        /*
         * We keep iterator in m->private, since normal case is to
@@ -557,7 +589,7 @@ static int kallsyms_open(struct inode *inode, struct file *file)
         * using get_symbol_offset for every symbol.
         */
        struct kallsym_iter *iter;
-       iter = __seq_open_private(file, &kallsyms_op, sizeof(*iter));
+       iter = __seq_open_private(file, ops, sizeof(*iter));
        if (!iter)
                return -ENOMEM;
        kallsyms_iter_reset(iter, 0);
@@ -565,6 +597,16 @@ static int kallsyms_open(struct inode *inode, struct file *file)
        return 0;
 }
 
+static int kallsyms_open(struct inode *inode, struct file *file)
+{
+       return kallsyms_open_internal(inode, file, &kallsyms_op);
+}
+
+static int kallmodsyms_open(struct inode *inode, struct file *file)
+{
+       return kallsyms_open_internal(inode, file, &kallmodsyms_op);
+}
+
 #ifdef CONFIG_KGDB_KDB
 const char *kdb_walk_kallsyms(loff_t *pos)
 {
@@ -592,9 +634,17 @@ static const struct file_operations kallsyms_operations = {
        .release = seq_release_private,
 };
 
+static const struct file_operations kallmodsyms_operations = {
+       .open = kallmodsyms_open,
+       .read = seq_read,
+       .llseek = seq_lseek,
+       .release = seq_release_private,
+};
+
 static int __init kallsyms_init(void)
 {
        proc_create("kallsyms", 0444, NULL, &kallsyms_operations);
+       proc_create("kallmodsyms", 0444, NULL, &kallmodsyms_operations);
        return 0;
 }
 device_initcall(kallsyms_init);
index 7fe1c538a2579b5546f4251ede5833ee04eae14c..0c2cf750db9d645a8c084629a663a945a2c35d52 100644 (file)
@@ -24,6 +24,9 @@ HOSTCFLAGS_asn1_compiler.o = -I$(srctree)/include
 
 always         := $(hostprogs-y) $(hostprogs-m)
 
+HOSTCFLAGS_kallsyms.o := $(shell pkg-config --cflags glib-2.0) -g
+HOSTLOADLIBES_kallsyms := $(shell pkg-config --libs glib-2.0) -ldw -g
+
 # The following hostprogs-y programs are only build on demand
 hostprogs-y += unifdef docproc
 
index 8fa81e84e29510c053e5e52cefc5eb9e1da16887..6975532f4e94a1c4d88f4e3d795c6a8979290715 100644 (file)
@@ -5,7 +5,9 @@
  * This software may be used and distributed according to the terms
  * of the GNU General Public License, incorporated herein by reference.
  *
- * Usage: nm -n vmlinux | scripts/kallsyms [--all-symbols] > symbols.S
+ * Usage: nm -n vmlinux | scripts/kallsyms [--all-symbols]
+ *                                         [--symbol-prefix=<prefix char>]
+ *                                         [--builtin=modules.builtin] > symbols.S
  *
  *      Table compression uses all the unused char codes on the symbols and
  *  maps these to the most used substrings (tokens). For instance, it might
 #include <stdlib.h>
 #include <string.h>
 #include <ctype.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include <libelf.h>
+#include <dwarf.h>
+#include <elfutils/libdwfl.h>
+#include <elfutils/libdw.h>
+#include <glib.h>
 
 #ifndef ARRAY_SIZE
 #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
@@ -34,6 +44,7 @@ struct sym_entry {
        unsigned int len;
        unsigned int start_pos;
        unsigned char *sym;
+       unsigned int module;
 };
 
 struct addr_range {
@@ -68,12 +79,32 @@ int token_profit[0x10000];
 unsigned char best_table[256][2];
 unsigned char best_table_len[256];
 
+/*
+ * The list of builtin module names.
+ */
+static char **builtin_modules;
+static unsigned int builtin_module_size, builtin_module_len;
+
+/*
+ * For each builtin module, its offset from the start of the builtin_module
+ * list, assuming consecutive placement.
+ */
+static unsigned int *builtin_module_offsets;
+
+/*
+ * A mapping from symbol name to the index of the module it is part of in the
+ * builtin_modules list.  Symbols within the same module share pointers to the
+ * same index allocation (thus this is nearly impossible to free safely, but
+ * quite space-efficient).
+ */
+static GHashTable *symbol_to_module;
 
 static void usage(void)
 {
        fprintf(stderr, "Usage: kallsyms [--all-symbols] "
                        "[--symbol-prefix=<prefix char>] "
                        "[--page-offset=<CONFIG_PAGE_OFFSET>] "
+                       "[--builtin=modules.builtin] "
                        "< in.map > out.S\n");
        exit(1);
 }
@@ -113,6 +144,7 @@ static int read_symbol(FILE *in, struct sym_entry *s)
 {
        char str[500];
        char *sym, stype;
+       unsigned int *module;
        int rc;
 
        rc = fscanf(in, "%llx %c %499s\n", &s->addr, &stype, str);
@@ -159,6 +191,14 @@ static int read_symbol(FILE *in, struct sym_entry *s)
        else if (stype == 'N')
                return -1;
 
+       /* look up the builtin module this is part of (if any). */
+       module = g_hash_table_lookup(symbol_to_module, sym);
+
+       if (module)
+               s->module = builtin_module_offsets[*module];
+       else
+               s->module = 0;
+
        /* include the type field in the symbol name, so that it gets
         * compressed together */
        s->len = strlen(str) + 1;
@@ -207,6 +247,8 @@ static int symbol_valid(struct sym_entry *s)
                "kallsyms_markers",
                "kallsyms_token_table",
                "kallsyms_token_index",
+               "kallsyms_symbol_modules",
+               "kallsyms_modules",
 
        /* Exclude linker generated symbols which vary between passes */
                "_SDA_BASE_",           /* ppc */
@@ -417,8 +459,17 @@ static void write_src(void)
        for (i = 0; i < 256; i++)
                printf("\t.short\t%d\n", best_idx[i]);
        printf("\n");
-}
 
+       output_label("kallsyms_modules");
+       for (i = 0; i < builtin_module_len; i++)
+               printf("\t.asciz\t\"%s\"\n", builtin_modules[i]);
+       printf("\n");
+
+       output_label("kallsyms_symbol_modules");
+       for (i = 0; i < table_cnt; i++)
+               printf("\t.int\t%d\n", table[i].module);
+       printf("\n");
+}
 
 /* table lookup compression functions */
 
@@ -685,12 +736,280 @@ static void make_percpus_absolute(void)
                        table[i].sym[0] = 'A';
 }
 
+/* Built-in module list computation. */
+
+/*
+ * Stub libdwfl callback, use only the ELF handle passed in.
+ */
+static int no_debuginfo(Dwfl_Module *mod,
+                       void **userdata,
+                       const char *modname,
+                       Dwarf_Addr base,
+                       const char *file_name,
+                       const char *debuglink_file,
+                       GElf_Word debuglink_crc,
+                       char **debuginfo_file_name)
+{
+       return -1;
+}
+
+/*
+ * Wrap up dwfl_new() complexities.
+ */
+static Dwfl *private_dwfl_new(const char *file_name)
+{
+       const char *err;
+       static Dwfl_Callbacks cb = { .find_debuginfo = no_debuginfo,
+                                    .section_address = dwfl_offline_section_address };
+       Dwfl *dwfl = dwfl_begin(&cb);
+
+       if (dwfl == NULL) {
+               err = "initialize libdwfl";
+               goto fail;
+       }
+
+       if (dwfl_report_elf(dwfl, "", file_name, -1, 0) == NULL) {
+               err = "open object file with libdwfl";
+               goto fail;
+       }
+
+       if (dwfl_report_end(dwfl, NULL, NULL) != 0) {
+               err = "finish opening object file with libdwfl";
+               goto fail;
+       }
+
+       return dwfl;
+ fail:
+       fprintf(stderr, "Cannot %s for %s: %s\n", err, file_name,
+               dwfl_errmsg(dwfl_errno()));
+       exit(1);
+}
+
+/*
+ * The converse of private_dwfl_new().
+ */
+static void private_dwfl_free(Dwfl *dwfl)
+{
+       dwfl_report_end(dwfl, NULL, NULL);
+}
+
+/*
+ * Populate the symbol_to_module mapping for one built-in module.
+ */
+static void read_module_symbols(unsigned int module_name,
+                               const char *module_path,
+                               GHashTable *module_symbol_seen)
+{
+       Dwfl *dwfl = private_dwfl_new(module_path);
+       Dwarf_Die *tu = NULL;
+       Dwarf_Addr junk;
+       unsigned int *module_idx = NULL;
+
+       while ((tu = dwfl_nextcu(dwfl, tu, &junk)) != NULL) {
+               Dwarf_Die toplevel;
+               int sib_ret;
+
+               if (dwarf_tag(tu) != DW_TAG_compile_unit) {
+                       fprintf(stderr, "Malformed DWARF: non-compile_unit at "
+                               "top level in %s.\n", module_path);
+                       exit(1);
+               }
+
+               switch (dwarf_child(tu, &toplevel)) {
+               case -1: fprintf(stderr, "Warning: looking for toplevel "
+                                "child of %s in %s: %s\n", dwarf_diename(tu),
+                                module_path, dwarf_errmsg(dwarf_errno()));
+                       continue;
+               case 1: /* No DIEs at all in this TU */
+                       continue;
+               default: /* Child DIE's exist. */
+                       break;
+               }
+
+               do {
+                       if (dwarf_tag(&toplevel) == DW_TAG_subprogram) {
+                               if (module_idx == NULL) {
+                                       module_idx = malloc(sizeof(unsigned int));
+                                       if (module_idx == NULL) {
+                                               fprintf(stderr, "out of memory\n");
+                                               exit(1);
+                                       }
+
+                                       *module_idx = module_name;
+                               }
+                               /*
+                                * If we have never seen this symbol before, we
+                                * note that we have seen it, and track it in
+                                * the symbol_to_module mapping.  Otherwise, we
+                                * *remove* it from that mapping, if it is
+                                * present there.
+                                */
+
+                               if (!g_hash_table_lookup_extended(module_symbol_seen,
+                                                                 dwarf_diename(&toplevel),
+                                                                 NULL, NULL)) {
+
+                                       g_hash_table_insert(module_symbol_seen,
+                                                           strdup(dwarf_diename(&toplevel)),
+                                                           NULL);
+
+                                       g_hash_table_insert(symbol_to_module,
+                                                           strdup(dwarf_diename(&toplevel)),
+                                                           module_idx);
+                               } else {
+                                       g_hash_table_remove(symbol_to_module,
+                                                           strdup(dwarf_diename(&toplevel)));
+                               }
+                       }
+               } while ((sib_ret = dwarf_siblingof(&toplevel, &toplevel)) == 0);
+
+               if (sib_ret == -1) {
+                       fprintf(stderr, "Warning: Cannot advance to next sibling "
+                               "of %s in %s: %s\n", dwarf_diename(&toplevel),
+                               module_path, dwarf_errmsg(dwarf_errno()));
+               }
+       }
+       private_dwfl_free(dwfl);
+}
+
+/*
+ * Expand the builtin modules list.
+ */
+static void expand_builtin_modules(void)
+{
+       builtin_module_size += 50;
+
+       builtin_modules = realloc(builtin_modules,
+                                 sizeof(*builtin_modules) *
+                                 builtin_module_size);
+       builtin_module_offsets = realloc(builtin_module_offsets,
+                                        sizeof(*builtin_module_offsets) *
+                                        builtin_module_size);
+
+       if (!builtin_modules || !builtin_module_offsets) {
+               fprintf(stderr, "out of memory\n");
+               exit(1);
+       }
+}
+
+/*
+ * Populate the symbol_to_module mapping for all built-in modules,
+ * and the list of built-in modules itself.
+ */
+static void read_modules(const char *modules_builtin)
+{
+       FILE *f;
+       char *line = NULL;
+       size_t line_size = 0;
+       size_t offset = 0;
+
+       /*
+        * A hash containing a mapping from a symbol name if that symbol has
+        * been seen in any modules so far.  This is used to filter out symbols
+        * found in more than one built-in module, on the grounds that they are
+        * probably symbols from the out-of-line representation of inline
+        * functions in kernel header files shared between modules.
+        */
+       GHashTable *module_symbol_seen;
+
+       symbol_to_module = g_hash_table_new(g_str_hash, g_str_equal);
+       module_symbol_seen = g_hash_table_new_full(g_str_hash, g_str_equal,
+                                                  free, NULL);
+
+       if (symbol_to_module == NULL || module_symbol_seen == NULL) {
+               fprintf(stderr, "Out of memory");
+               exit(1);
+       }
+
+       /*
+        * builtin_modules[0] is a null entry signifying a symbol that cannot be
+        * modular.
+        */
+       builtin_module_size = 50;
+       builtin_modules = malloc(sizeof(*builtin_modules) *
+                                builtin_module_size);
+       builtin_module_offsets = malloc(sizeof(*builtin_module_offsets) *
+                                builtin_module_size);
+       builtin_modules[0] = strdup("");
+       builtin_module_len = 1;
+       offset++;
+
+       f = fopen(modules_builtin, "r");
+       if (f == NULL) {
+               fprintf(stderr, "Cannot open builtin module file %s: "
+                       "%s\n", modules_builtin, strerror(errno));
+               exit(1);
+       }
+
+       /*
+        * Read in, stripping off the leading path element, if any, transforming
+        * the suffix into .o from .ko, and also computing the suffixless,
+        * pathless name of the module.  Any elements that don't have files
+        * corresponding to them elicit a warning, and do not have their names
+        * recorded.
+        */
+       while (getline(&line, &line_size, f) >= 0) {
+               if (line[0] != '\0') {
+                       char *first_slash;
+                       char *last_slash;
+                       char *last_dot;
+
+                       first_slash = strchr(line, '/');
+                       last_slash = strrchr(line, '/');
+
+                       first_slash = (!first_slash) ? line : first_slash + 1;
+                       last_slash = (!last_slash) ? line : last_slash + 1;
+
+                       last_dot = strrchr(line, '.');
+                       if ((last_dot != NULL) &&
+                           ((strcmp(last_dot, ".ko") == 0) ||
+                            (strcmp(last_dot, ".ko\n") == 0))) {
+                               strcpy(last_dot, ".o");
+                       }
+
+                       if (access(first_slash, R_OK) == 0) {
+                               char *module_name = strdup(last_slash);
+
+                               last_dot = strrchr(module_name, '.');
+                               if (last_dot != NULL)
+                                       *last_dot = '\0';
+
+                               read_module_symbols(builtin_module_len, first_slash,
+                                       module_symbol_seen);
+
+                               if (builtin_module_size >= builtin_module_len)
+                                       expand_builtin_modules();
+                               builtin_modules[builtin_module_len] = module_name;
+                               builtin_module_offsets[builtin_module_len] = offset;
+                               offset += strlen(module_name) + 1;
+                               builtin_module_len++;
+                       } else {
+                               fprintf(stderr, "%s population: module %s is not "
+                                       "readable.\n", modules_builtin,
+                                       first_slash);
+                       }
+               }
+       }
+       free(line);
+
+       if (ferror(f)) {
+               fprintf(stderr, "Error reading from %s: %s\n",
+                       modules_builtin, strerror(errno));
+               exit(1);
+       }
+
+       fclose(f);
+       g_hash_table_destroy(module_symbol_seen);
+}
+
 int main(int argc, char **argv)
 {
-       if (argc >= 2) {
+       const char *modules_builtin = "modules.builtin";
+
+       if (argc >= 1) {
                int i;
                for (i = 1; i < argc; i++) {
-                       if(strcmp(argv[i], "--all-symbols") == 0)
+                       if (strcmp(argv[i], "--all-symbols") == 0)
                                all_symbols = 1;
                        else if (strcmp(argv[i], "--absolute-percpu") == 0)
                                absolute_percpu = 1;
@@ -703,12 +1022,15 @@ int main(int argc, char **argv)
                        } else if (strncmp(argv[i], "--page-offset=", 14) == 0) {
                                const char *p = &argv[i][14];
                                kernel_start_addr = strtoull(p, NULL, 16);
-                       } else
+                       } else if (strncmp(argv[i], "--builtin=", 10) == 0)
+                               modules_builtin = &argv[i][10];
+                       else
                                usage();
                }
        } else if (argc != 1)
                usage();
 
+       read_modules(modules_builtin);
        read_map(stdin);
        if (absolute_percpu)
                make_percpus_absolute();