| // SPDX-License-Identifier: GPL-2.0-or-later | |
| /* | |
| * Module kallsyms support | |
| * | |
| * Copyright (C) 2010 Rusty Russell | |
| */ | |
| #include <linux/module.h> | |
| #include <linux/module_symbol.h> | |
| #include <linux/kallsyms.h> | |
| #include <linux/buildid.h> | |
| #include <linux/bsearch.h> | |
| #include "internal.h" | |
| /* Lookup exported symbol in given range of kernel_symbols */ | |
| static const struct kernel_symbol *lookup_exported_symbol(const char *name, | |
| const struct kernel_symbol *start, | |
| const struct kernel_symbol *stop) | |
| { | |
| return bsearch(name, start, stop - start, | |
| sizeof(struct kernel_symbol), cmp_name); | |
| } | |
| static int is_exported(const char *name, unsigned long value, | |
| const struct module *mod) | |
| { | |
| const struct kernel_symbol *ks; | |
| if (!mod) | |
| ks = lookup_exported_symbol(name, __start___ksymtab, __stop___ksymtab); | |
| else | |
| ks = lookup_exported_symbol(name, mod->syms, mod->syms + mod->num_syms); | |
| return ks && kernel_symbol_value(ks) == value; | |
| } | |
| /* As per nm */ | |
| static char elf_type(const Elf_Sym *sym, const struct load_info *info) | |
| { | |
| const Elf_Shdr *sechdrs = info->sechdrs; | |
| 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 || sym->st_shndx == info->index.pcpu) | |
| 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 (strstarts(info->secstrings + sechdrs[sym->st_shndx].sh_name, | |
| ".debug")) { | |
| return 'n'; | |
| } | |
| return '?'; | |
| } | |
| static bool is_core_symbol(const Elf_Sym *src, const Elf_Shdr *sechdrs, | |
| unsigned int shnum, unsigned int pcpundx) | |
| { | |
| const Elf_Shdr *sec; | |
| enum mod_mem_type type; | |
| if (src->st_shndx == SHN_UNDEF || | |
| src->st_shndx >= shnum || | |
| !src->st_name) | |
| return false; | |
| #ifdef CONFIG_KALLSYMS_ALL | |
| if (src->st_shndx == pcpundx) | |
| return true; | |
| #endif | |
| sec = sechdrs + src->st_shndx; | |
| type = sec->sh_entsize >> SH_ENTSIZE_TYPE_SHIFT; | |
| if (!(sec->sh_flags & SHF_ALLOC) | |
| #ifndef CONFIG_KALLSYMS_ALL | |
| || !(sec->sh_flags & SHF_EXECINSTR) | |
| #endif | |
| || mod_mem_type_is_init(type)) | |
| return false; | |
| return true; | |
| } | |
| /* | |
| * We only allocate and copy the strings needed by the parts of symtab | |
| * we keep. This is simple, but has the effect of making multiple | |
| * copies of duplicates. We could be more sophisticated, see | |
| * linux-kernel thread starting with | |
| * <73defb5e4bca04a6431392cc341112b1@localhost>. | |
| */ | |
| void layout_symtab(struct module *mod, struct load_info *info) | |
| { | |
| Elf_Shdr *symsect = info->sechdrs + info->index.sym; | |
| Elf_Shdr *strsect = info->sechdrs + info->index.str; | |
| const Elf_Sym *src; | |
| unsigned int i, nsrc, ndst, strtab_size = 0; | |
| struct module_memory *mod_mem_data = &mod->mem[MOD_DATA]; | |
| struct module_memory *mod_mem_init_data = &mod->mem[MOD_INIT_DATA]; | |
| /* Put symbol section at end of init part of module. */ | |
| symsect->sh_flags |= SHF_ALLOC; | |
| symsect->sh_entsize = module_get_offset_and_type(mod, MOD_INIT_DATA, | |
| symsect, info->index.sym); | |
| pr_debug("\t%s\n", info->secstrings + symsect->sh_name); | |
| src = (void *)info->hdr + symsect->sh_offset; | |
| nsrc = symsect->sh_size / sizeof(*src); | |
| /* Compute total space required for the core symbols' strtab. */ | |
| for (ndst = i = 0; i < nsrc; i++) { | |
| if (i == 0 || is_livepatch_module(mod) || | |
| is_core_symbol(src + i, info->sechdrs, info->hdr->e_shnum, | |
| info->index.pcpu)) { | |
| strtab_size += strlen(&info->strtab[src[i].st_name]) + 1; | |
| ndst++; | |
| } | |
| } | |
| /* Append room for core symbols at end of core part. */ | |
| info->symoffs = ALIGN(mod_mem_data->size, symsect->sh_addralign ?: 1); | |
| info->stroffs = mod_mem_data->size = info->symoffs + ndst * sizeof(Elf_Sym); | |
| mod_mem_data->size += strtab_size; | |
| /* Note add_kallsyms() computes strtab_size as core_typeoffs - stroffs */ | |
| info->core_typeoffs = mod_mem_data->size; | |
| mod_mem_data->size += ndst * sizeof(char); | |
| /* Put string table section at end of init part of module. */ | |
| strsect->sh_flags |= SHF_ALLOC; | |
| strsect->sh_entsize = module_get_offset_and_type(mod, MOD_INIT_DATA, | |
| strsect, info->index.str); | |
| pr_debug("\t%s\n", info->secstrings + strsect->sh_name); | |
| /* We'll tack temporary mod_kallsyms on the end. */ | |
| mod_mem_init_data->size = ALIGN(mod_mem_init_data->size, | |
| __alignof__(struct mod_kallsyms)); | |
| info->mod_kallsyms_init_off = mod_mem_init_data->size; | |
| mod_mem_init_data->size += sizeof(struct mod_kallsyms); | |
| info->init_typeoffs = mod_mem_init_data->size; | |
| mod_mem_init_data->size += nsrc * sizeof(char); | |
| } | |
| /* | |
| * We use the full symtab and strtab which layout_symtab arranged to | |
| * be appended to the init section. Later we switch to the cut-down | |
| * core-only ones. | |
| */ | |
| void add_kallsyms(struct module *mod, const struct load_info *info) | |
| { | |
| unsigned int i, ndst; | |
| const Elf_Sym *src; | |
| Elf_Sym *dst; | |
| char *s; | |
| Elf_Shdr *symsec = &info->sechdrs[info->index.sym]; | |
| unsigned long strtab_size; | |
| void *data_base = mod->mem[MOD_DATA].base; | |
| void *init_data_base = mod->mem[MOD_INIT_DATA].base; | |
| struct mod_kallsyms *kallsyms; | |
| kallsyms = init_data_base + info->mod_kallsyms_init_off; | |
| kallsyms->symtab = (void *)symsec->sh_addr; | |
| kallsyms->num_symtab = symsec->sh_size / sizeof(Elf_Sym); | |
| /* Make sure we get permanent strtab: don't use info->strtab. */ | |
| kallsyms->strtab = (void *)info->sechdrs[info->index.str].sh_addr; | |
| kallsyms->typetab = init_data_base + info->init_typeoffs; | |
| /* | |
| * Now populate the cut down core kallsyms for after init | |
| * and set types up while we still have access to sections. | |
| */ | |
| mod->core_kallsyms.symtab = dst = data_base + info->symoffs; | |
| mod->core_kallsyms.strtab = s = data_base + info->stroffs; | |
| mod->core_kallsyms.typetab = data_base + info->core_typeoffs; | |
| strtab_size = info->core_typeoffs - info->stroffs; | |
| src = kallsyms->symtab; | |
| for (ndst = i = 0; i < kallsyms->num_symtab; i++) { | |
| kallsyms->typetab[i] = elf_type(src + i, info); | |
| if (i == 0 || is_livepatch_module(mod) || | |
| is_core_symbol(src + i, info->sechdrs, info->hdr->e_shnum, | |
| info->index.pcpu)) { | |
| ssize_t ret; | |
| mod->core_kallsyms.typetab[ndst] = | |
| kallsyms->typetab[i]; | |
| dst[ndst] = src[i]; | |
| dst[ndst++].st_name = s - mod->core_kallsyms.strtab; | |
| ret = strscpy(s, &kallsyms->strtab[src[i].st_name], | |
| strtab_size); | |
| if (ret < 0) | |
| break; | |
| s += ret + 1; | |
| strtab_size -= ret + 1; | |
| } | |
| } | |
| /* Set up to point into init section. */ | |
| rcu_assign_pointer(mod->kallsyms, kallsyms); | |
| mod->core_kallsyms.num_symtab = ndst; | |
| } | |
| #if IS_ENABLED(CONFIG_STACKTRACE_BUILD_ID) | |
| void init_build_id(struct module *mod, const struct load_info *info) | |
| { | |
| const Elf_Shdr *sechdr; | |
| unsigned int i; | |
| for (i = 0; i < info->hdr->e_shnum; i++) { | |
| sechdr = &info->sechdrs[i]; | |
| if (!sect_empty(sechdr) && sechdr->sh_type == SHT_NOTE && | |
| !build_id_parse_buf((void *)sechdr->sh_addr, mod->build_id, | |
| sechdr->sh_size)) | |
| break; | |
| } | |
| } | |
| #else | |
| void init_build_id(struct module *mod, const struct load_info *info) | |
| { | |
| } | |
| #endif | |
| static const char *kallsyms_symbol_name(struct mod_kallsyms *kallsyms, unsigned int symnum) | |
| { | |
| return kallsyms->strtab + kallsyms->symtab[symnum].st_name; | |
| } | |
| /* | |
| * Given a module and address, find the corresponding symbol and return its name | |
| * while providing its size and offset if needed. | |
| */ | |
| static const char *find_kallsyms_symbol(struct module *mod, | |
| unsigned long addr, | |
| unsigned long *size, | |
| unsigned long *offset) | |
| { | |
| unsigned int i, best = 0; | |
| unsigned long nextval, bestval; | |
| struct mod_kallsyms *kallsyms = rcu_dereference(mod->kallsyms); | |
| struct module_memory *mod_mem = NULL; | |
| for_each_mod_mem_type(type) { | |
| #ifndef CONFIG_KALLSYMS_ALL | |
| if (!mod_mem_type_is_text(type)) | |
| continue; | |
| #endif | |
| if (within_module_mem_type(addr, mod, type)) { | |
| mod_mem = &mod->mem[type]; | |
| break; | |
| } | |
| } | |
| if (!mod_mem) | |
| return NULL; | |
| /* Initialize bounds within memory region the address belongs to. */ | |
| nextval = (unsigned long)mod_mem->base + mod_mem->size; | |
| bestval = (unsigned long)mod_mem->base - 1; | |
| /* | |
| * Scan for closest preceding symbol, and next symbol. (ELF | |
| * starts real symbols at 1). | |
| */ | |
| for (i = 1; i < kallsyms->num_symtab; i++) { | |
| 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 | |
| * and inserted at a whim. | |
| */ | |
| if (*kallsyms_symbol_name(kallsyms, i) == '\0' || | |
| is_mapping_symbol(kallsyms_symbol_name(kallsyms, i))) | |
| continue; | |
| if (thisval <= addr && thisval > bestval) { | |
| best = i; | |
| bestval = thisval; | |
| } | |
| if (thisval > addr && thisval < nextval) | |
| nextval = thisval; | |
| } | |
| if (!best) | |
| return NULL; | |
| if (size) | |
| *size = nextval - bestval; | |
| if (offset) | |
| *offset = addr - bestval; | |
| return kallsyms_symbol_name(kallsyms, best); | |
| } | |
| void * __weak dereference_module_function_descriptor(struct module *mod, | |
| void *ptr) | |
| { | |
| return ptr; | |
| } | |
| /* | |
| * For kallsyms to ask for address resolution. NULL means not found. Careful | |
| * not to lock to avoid deadlock on oopses, RCU is enough. | |
| */ | |
| int module_address_lookup(unsigned long addr, | |
| unsigned long *size, | |
| unsigned long *offset, | |
| char **modname, | |
| const unsigned char **modbuildid, | |
| char *namebuf) | |
| { | |
| const char *sym; | |
| int ret = 0; | |
| struct module *mod; | |
| guard(rcu)(); | |
| mod = __module_address(addr); | |
| if (mod) { | |
| if (modname) | |
| *modname = mod->name; | |
| if (modbuildid) | |
| *modbuildid = module_buildid(mod); | |
| sym = find_kallsyms_symbol(mod, addr, size, offset); | |
| if (sym) | |
| ret = strscpy(namebuf, sym, KSYM_NAME_LEN); | |
| } | |
| return ret; | |
| } | |
| int lookup_module_symbol_name(unsigned long addr, char *symname) | |
| { | |
| struct module *mod; | |
| guard(rcu)(); | |
| list_for_each_entry_rcu(mod, &modules, list) { | |
| if (mod->state == MODULE_STATE_UNFORMED) | |
| continue; | |
| if (within_module(addr, mod)) { | |
| const char *sym; | |
| sym = find_kallsyms_symbol(mod, addr, NULL, NULL); | |
| if (!sym) | |
| goto out; | |
| strscpy(symname, sym, KSYM_NAME_LEN); | |
| return 0; | |
| } | |
| } | |
| out: | |
| return -ERANGE; | |
| } | |
| int module_get_kallsym(unsigned int symnum, unsigned long *value, char *type, | |
| char *name, char *module_name, int *exported) | |
| { | |
| struct module *mod; | |
| guard(rcu)(); | |
| list_for_each_entry_rcu(mod, &modules, list) { | |
| struct mod_kallsyms *kallsyms; | |
| if (mod->state == MODULE_STATE_UNFORMED) | |
| continue; | |
| kallsyms = rcu_dereference(mod->kallsyms); | |
| if (symnum < kallsyms->num_symtab) { | |
| const Elf_Sym *sym = &kallsyms->symtab[symnum]; | |
| *value = kallsyms_symbol_value(sym); | |
| *type = kallsyms->typetab[symnum]; | |
| strscpy(name, kallsyms_symbol_name(kallsyms, symnum), KSYM_NAME_LEN); | |
| strscpy(module_name, mod->name, MODULE_NAME_LEN); | |
| *exported = is_exported(name, *value, mod); | |
| return 0; | |
| } | |
| symnum -= kallsyms->num_symtab; | |
| } | |
| return -ERANGE; | |
| } | |
| /* Given a module and name of symbol, find and return the symbol's value */ | |
| static unsigned long __find_kallsyms_symbol_value(struct module *mod, const char *name) | |
| { | |
| unsigned int i; | |
| struct mod_kallsyms *kallsyms = rcu_dereference(mod->kallsyms); | |
| for (i = 0; i < kallsyms->num_symtab; i++) { | |
| const Elf_Sym *sym = &kallsyms->symtab[i]; | |
| if (strcmp(name, kallsyms_symbol_name(kallsyms, i)) == 0 && | |
| sym->st_shndx != SHN_UNDEF) | |
| return kallsyms_symbol_value(sym); | |
| } | |
| return 0; | |
| } | |
| static unsigned long __module_kallsyms_lookup_name(const char *name) | |
| { | |
| struct module *mod; | |
| char *colon; | |
| colon = strnchr(name, MODULE_NAME_LEN, ':'); | |
| if (colon) { | |
| mod = find_module_all(name, colon - name, false); | |
| if (mod) | |
| return __find_kallsyms_symbol_value(mod, colon + 1); | |
| return 0; | |
| } | |
| list_for_each_entry_rcu(mod, &modules, list) { | |
| unsigned long ret; | |
| if (mod->state == MODULE_STATE_UNFORMED) | |
| continue; | |
| ret = __find_kallsyms_symbol_value(mod, name); | |
| if (ret) | |
| return ret; | |
| } | |
| return 0; | |
| } | |
| /* Look for this name: can be of form module:name. */ | |
| unsigned long module_kallsyms_lookup_name(const char *name) | |
| { | |
| /* Don't lock: we're in enough trouble already. */ | |
| guard(rcu)(); | |
| return __module_kallsyms_lookup_name(name); | |
| } | |
| unsigned long find_kallsyms_symbol_value(struct module *mod, const char *name) | |
| { | |
| guard(rcu)(); | |
| return __find_kallsyms_symbol_value(mod, name); | |
| } | |
| int module_kallsyms_on_each_symbol(const char *modname, | |
| int (*fn)(void *, const char *, unsigned long), | |
| void *data) | |
| { | |
| struct module *mod; | |
| unsigned int i; | |
| int ret = 0; | |
| mutex_lock(&module_mutex); | |
| list_for_each_entry(mod, &modules, list) { | |
| struct mod_kallsyms *kallsyms; | |
| if (mod->state == MODULE_STATE_UNFORMED) | |
| continue; | |
| if (modname && strcmp(modname, mod->name)) | |
| continue; | |
| kallsyms = rcu_dereference_check(mod->kallsyms, | |
| lockdep_is_held(&module_mutex)); | |
| for (i = 0; i < kallsyms->num_symtab; i++) { | |
| const Elf_Sym *sym = &kallsyms->symtab[i]; | |
| if (sym->st_shndx == SHN_UNDEF) | |
| continue; | |
| ret = fn(data, kallsyms_symbol_name(kallsyms, i), | |
| kallsyms_symbol_value(sym)); | |
| if (ret != 0) | |
| goto out; | |
| } | |
| /* | |
| * The given module is found, the subsequent modules do not | |
| * need to be compared. | |
| */ | |
| if (modname) | |
| break; | |
| } | |
| out: | |
| mutex_unlock(&module_mutex); | |
| return ret; | |
| } |