ARM: module: Fix function kallsyms on Thumb-2

Thumb-2 functions have the lowest bit set in the symbol value in the
symtab.  When kallsyms are generated for the vmlinux, the kallsyms are
generated from the output of nm, and nm clears the lowest bit.

 $ arm-linux-gnueabihf-readelf -a vmlinux | grep show_interrupts
  95947: 8015dc89   686 FUNC    GLOBAL DEFAULT    2 show_interrupts
 $ arm-linux-gnueabihf-nm vmlinux | grep show_interrupts
 8015dc88 T show_interrupts
 $ cat /proc/kallsyms | grep show_interrupts
 8015dc88 T show_interrupts

However, for modules, the kallsyms uses the values in the symbol table
without modification, so for functions in modules, the lowest bit is set
in kallsyms.

 $ arm-linux-gnueabihf-readelf -a drivers/net/tun.ko | grep tun_get_socket
    333: 00002d4d    36 FUNC    GLOBAL DEFAULT    1 tun_get_socket
 $ arm-linux-gnueabihf-nm drivers/net/tun.ko | grep tun_get_socket
 00002d4c T tun_get_socket
 $ cat /proc/kallsyms | grep tun_get_socket
 7f802d4d t tun_get_socket      [tun]

Because of this, the symbol+offset of the crashing instruction shown in
oopses is incorrect when the crash is in a module.  For example, given a
tun_get_socket which starts like this,

 00002d4c <tun_get_socket>:
     2d4c:       6943            ldr     r3, [r0, #20]
     2d4e:       4a07            ldr     r2, [pc, #28]
     2d50:       4293            cmp     r3, r2

a crash when tun_get_socket is called with NULL results in:

 PC is at tun_xdp+0xa3/0xa4 [tun]
 pc : [<7f802d4c>]

As can be seen, the "PC is at" line reports the wrong symbol name, and
the symbol+offset will point to the wrong source line if it is passed to
gdb.

To solve this, add a way for archs to fixup the reading of these module
kallsyms values, and use that to clear the lowest bit for function
symbols on Thumb-2.

After the fix:

 # cat /proc/kallsyms | grep tun_get_socket
 7f802d4c t tun_get_socket       [tun]

 PC is at tun_get_socket+0x0/0x24 [tun]
 pc : [<7f802d4c>]

Signed-off-by: Vincent Whitchurch <vincent.whitchurch@axis.com>
Signed-off-by: Jessica Yu <jeyu@kernel.org>
This commit is contained in:
Vincent Whitchurch 2018-12-14 17:05:55 +01:00 committed by Jessica Yu
parent 5439c985c5
commit 93d77e7f14
3 changed files with 45 additions and 16 deletions

View File

@ -61,4 +61,15 @@ u32 get_module_plt(struct module *mod, unsigned long loc, Elf32_Addr val);
MODULE_ARCH_VERMAGIC_ARMTHUMB \
MODULE_ARCH_VERMAGIC_P2V
#ifdef CONFIG_THUMB2_KERNEL
#define HAVE_ARCH_KALLSYMS_SYMBOL_VALUE
static inline unsigned long kallsyms_symbol_value(const Elf_Sym *sym)
{
if (ELF_ST_TYPE(sym->st_info) == STT_FUNC)
return sym->st_value & ~1;
return sym->st_value;
}
#endif
#endif /* _ASM_ARM_MODULE_H */

View File

@ -486,6 +486,13 @@ struct module {
#define MODULE_ARCH_INIT {}
#endif
#ifndef HAVE_ARCH_KALLSYMS_SYMBOL_VALUE
static inline unsigned long kallsyms_symbol_value(const Elf_Sym *sym)
{
return sym->st_value;
}
#endif
extern struct mutex module_mutex;
/* FIXME: It'd be nice to isolate modules during init, too, so they

View File

@ -3928,7 +3928,7 @@ static const char *find_kallsyms_symbol(struct module *mod,
unsigned long *offset)
{
unsigned int i, best = 0;
unsigned long nextval;
unsigned long nextval, bestval;
struct mod_kallsyms *kallsyms = rcu_dereference_sched(mod->kallsyms);
/* At worse, next value is at end of module */
@ -3937,10 +3937,15 @@ static const char *find_kallsyms_symbol(struct module *mod,
else
nextval = (unsigned long)mod->core_layout.base+mod->core_layout.text_size;
bestval = kallsyms_symbol_value(&kallsyms->symtab[best]);
/* Scan for closest preceding symbol, and next symbol. (ELF
starts real symbols at 1). */
for (i = 1; i < kallsyms->num_symtab; i++) {
if (kallsyms->symtab[i].st_shndx == SHN_UNDEF)
const Elf_Sym *sym = &kallsyms->symtab[i];
unsigned long thisval = kallsyms_symbol_value(sym);
if (sym->st_shndx == SHN_UNDEF)
continue;
/* We ignore unnamed symbols: they're uninformative
@ -3949,21 +3954,21 @@ static const char *find_kallsyms_symbol(struct module *mod,
|| is_arm_mapping_symbol(kallsyms_symbol_name(kallsyms, i)))
continue;
if (kallsyms->symtab[i].st_value <= addr
&& kallsyms->symtab[i].st_value > kallsyms->symtab[best].st_value)
if (thisval <= addr && thisval > bestval) {
best = i;
if (kallsyms->symtab[i].st_value > addr
&& kallsyms->symtab[i].st_value < nextval)
nextval = kallsyms->symtab[i].st_value;
bestval = thisval;
}
if (thisval > addr && thisval < nextval)
nextval = thisval;
}
if (!best)
return NULL;
if (size)
*size = nextval - kallsyms->symtab[best].st_value;
*size = nextval - bestval;
if (offset)
*offset = addr - kallsyms->symtab[best].st_value;
*offset = addr - bestval;
return kallsyms_symbol_name(kallsyms, best);
}
@ -4069,8 +4074,10 @@ int module_get_kallsym(unsigned int symnum, unsigned long *value, char *type,
continue;
kallsyms = rcu_dereference_sched(mod->kallsyms);
if (symnum < kallsyms->num_symtab) {
*value = kallsyms->symtab[symnum].st_value;
*type = kallsyms->symtab[symnum].st_size;
const Elf_Sym *sym = &kallsyms->symtab[symnum];
*value = kallsyms_symbol_value(sym);
*type = sym->st_size;
strlcpy(name, kallsyms_symbol_name(kallsyms, symnum), KSYM_NAME_LEN);
strlcpy(module_name, mod->name, MODULE_NAME_LEN);
*exported = is_exported(name, *value, mod);
@ -4089,10 +4096,13 @@ static unsigned long find_kallsyms_symbol_value(struct module *mod, const char *
unsigned int i;
struct mod_kallsyms *kallsyms = rcu_dereference_sched(mod->kallsyms);
for (i = 0; i < kallsyms->num_symtab; i++)
for (i = 0; i < kallsyms->num_symtab; i++) {
const Elf_Sym *sym = &kallsyms->symtab[i];
if (strcmp(name, kallsyms_symbol_name(kallsyms, i)) == 0 &&
kallsyms->symtab[i].st_shndx != SHN_UNDEF)
return kallsyms->symtab[i].st_value;
sym->st_shndx != SHN_UNDEF)
return kallsyms_symbol_value(sym);
}
return 0;
}
@ -4137,12 +4147,13 @@ int module_kallsyms_on_each_symbol(int (*fn)(void *, const char *,
if (mod->state == MODULE_STATE_UNFORMED)
continue;
for (i = 0; i < kallsyms->num_symtab; i++) {
const Elf_Sym *sym = &kallsyms->symtab[i];
if (kallsyms->symtab[i].st_shndx == SHN_UNDEF)
if (sym->st_shndx == SHN_UNDEF)
continue;
ret = fn(data, kallsyms_symbol_name(kallsyms, i),
mod, kallsyms->symtab[i].st_value);
mod, kallsyms_symbol_value(sym));
if (ret != 0)
return ret;
}