Commit 51726557 authored by Rusty Russell's avatar Rusty Russell Committed by Linus Torvalds

[PATCH] kallsyms in proc

 This adds a /proc/kallsyms if you have CONFIG_KALLSYMS in your
kernel.  The output is nm-like, with symbols in caps (global) if
exported using EXPORT_SYMBOL, rather than the normal static
vs. non-static differentiation.

This is useful for things like performance monitoring tools (profiling
etc) that want to match addresses to names in user space.
parent 0b8ffff9
...@@ -266,6 +266,13 @@ static inline int module_is_live(struct module *mod) ...@@ -266,6 +266,13 @@ static inline int module_is_live(struct module *mod)
/* Is this address in a module? */ /* Is this address in a module? */
struct module *module_text_address(unsigned long addr); struct module *module_text_address(unsigned long addr);
/* Returns module and fills in value, defined and namebuf, or NULL if
symnum out of range. */
struct module *module_get_kallsym(unsigned int symnum,
unsigned long *value,
char *type,
char namebuf[128]);
int is_exported(const char *name, const struct module *mod);
#ifdef CONFIG_MODULE_UNLOAD #ifdef CONFIG_MODULE_UNLOAD
unsigned int module_refcount(struct module *mod); unsigned int module_refcount(struct module *mod);
...@@ -411,6 +418,19 @@ static inline const char *module_address_lookup(unsigned long addr, ...@@ -411,6 +418,19 @@ static inline const char *module_address_lookup(unsigned long addr,
return NULL; return NULL;
} }
static inline struct module *module_get_kallsym(unsigned int symnum,
unsigned long *value,
char *type,
char namebuf[128])
{
return NULL;
}
static inline int is_exported(const char *name, const struct module *mod)
{
return 0;
}
static inline int register_module_notifier(struct notifier_block * nb) static inline int register_module_notifier(struct notifier_block * nb)
{ {
/* no events will happen anyway, so this can always succeed */ /* no events will happen anyway, so this can always succeed */
......
...@@ -8,6 +8,11 @@ ...@@ -8,6 +8,11 @@
*/ */
#include <linux/kallsyms.h> #include <linux/kallsyms.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/init.h>
#include <linux/seq_file.h>
#include <linux/fs.h>
#include <linux/err.h>
#include <linux/proc_fs.h>
/* These will be re-linked against their real values during the second link stage */ /* These will be re-linked against their real values during the second link stage */
extern unsigned long kallsyms_addresses[] __attribute__((weak)); extern unsigned long kallsyms_addresses[] __attribute__((weak));
...@@ -117,5 +122,170 @@ void __print_symbol(const char *fmt, unsigned long address) ...@@ -117,5 +122,170 @@ void __print_symbol(const char *fmt, unsigned long address)
} }
} }
/* To avoid O(n^2) iteration, we carry prefix along. */
struct kallsym_iter
{
loff_t pos;
struct module *owner;
unsigned long value;
unsigned int nameoff; /* If iterating in core kernel symbols */
char type;
char name[128];
};
/* Only label it "global" if it is exported. */
static void upcase_if_global(struct kallsym_iter *iter)
{
if (is_exported(iter->name, iter->owner))
iter->type += 'A' - 'a';
}
static int get_ksymbol_mod(struct kallsym_iter *iter)
{
iter->owner = module_get_kallsym(iter->pos - kallsyms_num_syms,
&iter->value,
&iter->type, iter->name);
if (iter->owner == NULL)
return 0;
upcase_if_global(iter);
return 1;
}
static void get_ksymbol_core(struct kallsym_iter *iter)
{
unsigned stemlen;
/* First char of each symbol name indicates prefix length
shared with previous name (stem compresion). */
stemlen = kallsyms_names[iter->nameoff++];
strlcpy(iter->name+stemlen, kallsyms_names+iter->nameoff, 128-stemlen);
iter->nameoff += strlen(kallsyms_names + iter->nameoff) + 1;
iter->owner = NULL;
iter->value = kallsyms_addresses[iter->pos];
iter->type = 't';
upcase_if_global(iter);
}
static void reset_iter(struct kallsym_iter *iter)
{
iter->name[0] = '\0';
iter->nameoff = 0;
iter->pos = 0;
}
/* Returns false if pos at or past end of file. */
static int update_iter(struct kallsym_iter *iter, loff_t pos)
{
/* Module symbols can be accessed randomly. */
if (pos >= kallsyms_num_syms) {
iter->pos = pos;
return get_ksymbol_mod(iter);
}
/* If we're past the desired position, reset to start. */
if (pos < iter->pos)
reset_iter(iter);
/* We need to iterate through the previous symbols. */
for (; iter->pos <= pos; iter->pos++)
get_ksymbol_core(iter);
return 1;
}
static void *s_next(struct seq_file *m, void *p, loff_t *pos)
{
(*pos)++;
if (!update_iter(m->private, *pos))
return NULL;
return p;
}
static void *s_start(struct seq_file *m, loff_t *pos)
{
if (!update_iter(m->private, *pos))
return NULL;
return m->private;
}
static void s_stop(struct seq_file *m, void *p)
{
}
static int s_show(struct seq_file *m, void *p)
{
struct kallsym_iter *iter = m->private;
/* Some debugging symbols have no name. Ignore them. */
if (!iter->name[0])
return 0;
if (iter->owner)
seq_printf(m, "%0*lx %c %s\t[%s]\n",
(int)(2*sizeof(void*)),
iter->value, iter->type, iter->name,
module_name(iter->owner));
else
seq_printf(m, "%0*lx %c %s\n",
(int)(2*sizeof(void*)),
iter->value, iter->type, iter->name);
return 0;
}
struct seq_operations kallsyms_op = {
.start = s_start,
.next = s_next,
.stop = s_stop,
.show = s_show
};
static int kallsyms_open(struct inode *inode, struct file *file)
{
/* We keep iterator in m->private, since normal case is to
* s_start from where we left off, so we avoid O(N^2). */
struct kallsym_iter *iter;
int ret;
iter = kmalloc(sizeof(*iter), GFP_KERNEL);
if (!iter)
return -ENOMEM;
ret = seq_open(file, &kallsyms_op);
if (ret == 0)
((struct seq_file *)file->private_data)->private = iter;
else
kfree(iter);
return ret;
}
static int kallsyms_release(struct inode *inode, struct file *file)
{
struct seq_file *m = (struct seq_file *)file->private_data;
kfree(m->private);
return seq_release(inode, file);
}
static struct file_operations kallsyms_operations = {
.open = kallsyms_open,
.read = seq_read,
.llseek = seq_lseek,
.release = kallsyms_release,
};
int __init kallsyms_init(void)
{
struct proc_dir_entry *entry;
/* root-only: could chew up lots of cpu by read, seek back, read... */
entry = create_proc_entry("kallsyms", 0400, NULL);
if (entry)
entry->proc_fops = &kallsyms_operations;
return 0;
}
__initcall(kallsyms_init);
EXPORT_SYMBOL(kallsyms_lookup); EXPORT_SYMBOL(kallsyms_lookup);
EXPORT_SYMBOL(__print_symbol); EXPORT_SYMBOL(__print_symbol);
...@@ -1274,6 +1274,83 @@ static char *get_modinfo(Elf_Shdr *sechdrs, ...@@ -1274,6 +1274,83 @@ static char *get_modinfo(Elf_Shdr *sechdrs,
return NULL; return NULL;
} }
#ifdef CONFIG_KALLSYMS
int is_exported(const char *name, const struct module *mod)
{
unsigned int i;
if (!mod) {
for (i = 0; __start___ksymtab+i < __stop___ksymtab; i++)
if (strcmp(__start___ksymtab[i].name, name) == 0)
return 1;
return 0;
}
for (i = 0; i < mod->num_syms; i++)
if (strcmp(mod->syms[i].name, name) == 0)
return 1;
return 0;
}
/* As per nm */
static char elf_type(const Elf_Sym *sym,
Elf_Shdr *sechdrs,
const char *secstrings,
struct module *mod)
{
if (ELF_ST_BIND(sym->st_info) == STB_WEAK) {
if (ELF_ST_TYPE(sym->st_info) == STT_OBJECT)
return 'v';
else
return 'w';
}
if (sym->st_shndx == SHN_UNDEF)
return 'U';
if (sym->st_shndx == SHN_ABS)
return 'a';
if (sym->st_shndx >= SHN_LORESERVE)
return '?';
if (sechdrs[sym->st_shndx].sh_flags & SHF_EXECINSTR)
return 't';
if (sechdrs[sym->st_shndx].sh_flags & SHF_ALLOC
&& sechdrs[sym->st_shndx].sh_type != SHT_NOBITS) {
if (!(sechdrs[sym->st_shndx].sh_flags & SHF_WRITE))
return 'r';
else if (sechdrs[sym->st_shndx].sh_flags & ARCH_SHF_SMALL)
return 'g';
else
return 'd';
}
if (sechdrs[sym->st_shndx].sh_type == SHT_NOBITS) {
if (sechdrs[sym->st_shndx].sh_flags & ARCH_SHF_SMALL)
return 's';
else
return 'b';
}
if (strncmp(secstrings + sechdrs[sym->st_shndx].sh_name,
".debug", strlen(".debug")) == 0)
return 'n';
return '?';
}
static void add_kallsyms(struct module *mod,
Elf_Shdr *sechdrs,
unsigned int symindex,
unsigned int strindex,
const char *secstrings)
{
unsigned int i;
mod->symtab = (void *)sechdrs[symindex].sh_addr;
mod->num_symtab = sechdrs[symindex].sh_size / sizeof(Elf_Sym);
mod->strtab = (void *)sechdrs[strindex].sh_addr;
/* Set types up while we still have access to sections. */
for (i = 0; i < mod->num_symtab; i++)
mod->symtab[i].st_info
= elf_type(&mod->symtab[i], sechdrs, secstrings, mod);
}
#endif
/* Allocate and load the module: note that size of section 0 is always /* Allocate and load the module: note that size of section 0 is always
zero, and we rely on this for optional sections. */ zero, and we rely on this for optional sections. */
static struct module *load_module(void __user *umod, static struct module *load_module(void __user *umod,
...@@ -1525,15 +1602,12 @@ static struct module *load_module(void __user *umod, ...@@ -1525,15 +1602,12 @@ static struct module *load_module(void __user *umod,
percpu_modcopy(mod->percpu, (void *)sechdrs[pcpuindex].sh_addr, percpu_modcopy(mod->percpu, (void *)sechdrs[pcpuindex].sh_addr,
sechdrs[pcpuindex].sh_size); sechdrs[pcpuindex].sh_size);
#ifdef CONFIG_KALLSYMS
mod->symtab = (void *)sechdrs[symindex].sh_addr;
mod->num_symtab = sechdrs[symindex].sh_size / sizeof(Elf_Sym);
mod->strtab = (void *)sechdrs[strindex].sh_addr;
#endif
err = module_finalize(hdr, sechdrs, mod); err = module_finalize(hdr, sechdrs, mod);
if (err < 0) if (err < 0)
goto cleanup; goto cleanup;
add_kallsyms(mod, sechdrs, symindex, strindex, secstrings);
mod->args = args; mod->args = args;
if (obsparmindex) { if (obsparmindex) {
err = obsolete_params(mod->name, mod->args, err = obsolete_params(mod->name, mod->args,
...@@ -1715,6 +1789,30 @@ const char *module_address_lookup(unsigned long addr, ...@@ -1715,6 +1789,30 @@ const char *module_address_lookup(unsigned long addr,
} }
return NULL; return NULL;
} }
struct module *module_get_kallsym(unsigned int symnum,
unsigned long *value,
char *type,
char namebuf[128])
{
struct module *mod;
down(&module_mutex);
list_for_each_entry(mod, &modules, list) {
if (symnum < mod->num_symtab) {
*value = mod->symtab[symnum].st_value;
*type = mod->symtab[symnum].st_info;
strncpy(namebuf,
mod->strtab + mod->symtab[symnum].st_name,
127);
up(&module_mutex);
return mod;
}
symnum -= mod->num_symtab;
}
up(&module_mutex);
return NULL;
}
#endif /* CONFIG_KALLSYMS */ #endif /* CONFIG_KALLSYMS */
/* Called by the /proc file system to return a list of modules. */ /* Called by the /proc file system to return a list of modules. */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment