From: Nick Alcock Date: Fri, 11 May 2012 20:21:33 +0000 (+0100) Subject: kallsyms: add /proc/kallmodsyms X-Git-Tag: v4.1.12-92~313^2~150 X-Git-Url: https://www.infradead.org/git/?a=commitdiff_plain;h=f8dd5924eeb921c265a5d28e5099d7710945dc6a;p=users%2Fjedix%2Flinux-maple.git kallsyms: add /proc/kallmodsyms 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 --- diff --git a/Documentation/Changes b/Documentation/Changes index 646cdaa6e9d1..4db80e85cd57 100644 --- a/Documentation/Changes +++ b/Documentation/Changes @@ -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 +elfutils (build-time only) +-------- +o + +pkg-config (build-time only) +---------- +o + +glib (build-time only: 2.x required, not 3.x) +---- +o diff --git a/include/linux/kallsyms.h b/include/linux/kallsyms.h index 4dcdda928f09..8472c3dbace9 100644 --- a/include/linux/kallsyms.h +++ b/include/linux/kallsyms.h @@ -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; }; diff --git a/kernel/kallsyms.c b/kernel/kallsyms.c index 7b92a0f0c43d..cf4b3a7e6a06 100644 --- a/kernel/kallsyms.c +++ b/kernel/kallsyms.c @@ -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); diff --git a/scripts/Makefile b/scripts/Makefile index 7fe1c538a257..0c2cf750db9d 100644 --- a/scripts/Makefile +++ b/scripts/Makefile @@ -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 diff --git a/scripts/kallsyms.c b/scripts/kallsyms.c index 8fa81e84e295..6975532f4e94 100644 --- a/scripts/kallsyms.c +++ b/scripts/kallsyms.c @@ -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=] + * [--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 @@ -22,6 +24,14 @@ #include #include #include +#include +#include + +#include +#include +#include +#include +#include #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=] " "[--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();