| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Obtain symbols from vmlinux for usage by kallsyms. Replaces mksysmap. | |
| * | |
| * To retain compatibility, it provides the same output as nm, only faster. | |
| */ | |
| #include <ctype.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <xalloc.h> | |
| #include "elf-parse.h" | |
| #include "kallsyms.h" | |
| /* | |
| * What mksysmap dropped from System.map, by name: symbols that are not needed | |
| * there, or not suitable for kallsyms. | |
| */ | |
| static const char *const sysmap_omit_prefixes[] = { | |
| /* local symbols for ARM, MIPS, etc. */ | |
| "$", | |
| /* local labels, .LBB, .Ltmpxxx, .L__unnamed_xx, .LASANPC, etc. */ | |
| ".L", | |
| /* arm64 EFI stub namespace */ | |
| "__efistub_", | |
| /* arm64 local symbols in PIE namespace */ | |
| "__pi_$", | |
| "__pi_.L", | |
| /* arm64 local symbols in non-VHE KVM namespace */ | |
| "__kvm_nvhe_$", | |
| "__kvm_nvhe_.L", | |
| /* CFI type identifiers */ | |
| "__kcfi_typeid_", | |
| "__kvm_nvhe___kcfi_typeid_", | |
| "__pi___kcfi_typeid_", | |
| /* CRC from modversions */ | |
| "__crc_", | |
| /* EXPORT_SYMBOL (symbol name) */ | |
| "__kstrtab_", | |
| /* EXPORT_SYMBOL (namespace) */ | |
| "__kstrtabns_", | |
| /* MODULE_DEVICE_TABLE (symbol name) */ | |
| "__mod_device_table__", | |
| }; | |
| static const char *const sysmap_omit_suffixes[] = { | |
| /* arm */ | |
| "_from_arm", | |
| "_from_thumb", | |
| "_veneer", | |
| }; | |
| static const char *const sysmap_omit_names[] = { | |
| /* for LoongArch? */ | |
| "L0", | |
| /* ppc */ | |
| "_SDA_BASE_", | |
| "_SDA2_BASE_", | |
| }; | |
| /* Symbols that contain the pattern anywhere are dropped. */ | |
| static const char *const sysmap_omit_patterns[] = { | |
| /* ppc stub */ | |
| ".long_branch.", | |
| ".plt_branch.", | |
| }; | |
| /* __<alnum>*Thunk_: the linker's range extension thunks on arm. */ | |
| static bool is_range_thunk(const char *name) | |
| { | |
| const char *p; | |
| if (!string_starts_with(name, "__")) | |
| return false; | |
| for (p = name + 2; isalnum((unsigned char)*p); p++) | |
| ; | |
| return p - name >= 7 && *p == '_' && strncmp(p - 5, "Thunk", 5) == 0; | |
| } | |
| /* __UNIQUE_ID_modinfo_<n>: the MODULE_INFO() strings of built-in code. */ | |
| static bool is_modinfo_id(const char *name) | |
| { | |
| static const char prefix[] = "__UNIQUE_ID_modinfo_"; | |
| const char *p; | |
| if (!string_starts_with(name, prefix)) | |
| return false; | |
| for (p = name + strlen(prefix); isdigit((unsigned char)*p); p++) | |
| ; | |
| return !*p; | |
| } | |
| static bool sysmap_omits(const char *name, char type) | |
| { | |
| size_t i; | |
| /* Absolute, undefined and debugging symbols. */ | |
| if (type == 'a' || type == 'N' || type == 'U' || type == 'w') | |
| return true; | |
| for (i = 0; i < ARRAY_SIZE(sysmap_omit_prefixes); i++) | |
| if (string_starts_with(name, sysmap_omit_prefixes[i])) | |
| return true; | |
| for (i = 0; i < ARRAY_SIZE(sysmap_omit_suffixes); i++) | |
| if (string_ends_with(name, sysmap_omit_suffixes[i])) | |
| return true; | |
| for (i = 0; i < ARRAY_SIZE(sysmap_omit_names); i++) | |
| if (strcmp(name, sysmap_omit_names[i]) == 0) | |
| return true; | |
| for (i = 0; i < ARRAY_SIZE(sysmap_omit_patterns); i++) | |
| if (strstr(name, sysmap_omit_patterns[i])) | |
| return true; | |
| return is_range_thunk(name) || is_modinfo_id(name); | |
| } | |
| /* nm's letter for a symbol defined in a section, as BFD classifies it. */ | |
| static char section_symbol_type(Elf_Shdr *shdr, const char *secname) | |
| { | |
| static const char *const debug_prefixes[] = { | |
| ".debug", ".zdebug", ".gnu.debuglto_.debug_", | |
| ".gnu.linkonce.wi.", ".line", ".stab", | |
| }; | |
| const uint64_t flags = shdr_flags(shdr); | |
| size_t i; | |
| if (flags & SHF_EXECINSTR) | |
| return 't'; | |
| if (flags & SHF_ALLOC) { | |
| if (shdr_type(shdr) == SHT_NOBITS) | |
| return 'b'; | |
| return flags & SHF_WRITE ? 'd' : 'r'; | |
| } | |
| for (i = 0; i < ARRAY_SIZE(debug_prefixes); i++) | |
| if (string_starts_with(secname, debug_prefixes[i])) | |
| return 'N'; | |
| if (shdr_type(shdr) != SHT_NOBITS && !(flags & SHF_WRITE)) | |
| return 'n'; | |
| return '?'; | |
| } | |
| /* The letter nm prints for a symbol, or 0 for one it leaves out. */ | |
| static char elf_symbol_type(const struct elf_file *elf, Elf_Sym *sym) | |
| { | |
| unsigned int bind = sym_bind(sym), type = sym_type(sym); | |
| unsigned int shndx = sym_shndx(sym); | |
| Elf_Shdr *shdr; | |
| char c; | |
| if (type == STT_SECTION || type == STT_FILE) | |
| return 0; | |
| if (shndx == SHN_COMMON) | |
| return 'C'; | |
| if (shndx == SHN_UNDEF) { | |
| if (bind == STB_WEAK) | |
| return type == STT_OBJECT ? 'v' : 'w'; | |
| return 'U'; | |
| } | |
| if (type == STT_GNU_IFUNC) | |
| return 'i'; | |
| if (bind == STB_WEAK) | |
| return type == STT_OBJECT ? 'V' : 'W'; | |
| if (bind == STB_GNU_UNIQUE) | |
| return 'u'; | |
| if (bind != STB_GLOBAL && bind != STB_LOCAL) | |
| return '?'; | |
| if (shndx == SHN_ABS) { | |
| c = 'a'; | |
| } else if (shndx < elf->shnum) { | |
| shdr = elf_section(elf, shndx); | |
| c = section_symbol_type(shdr, elf_section_name(elf, shdr)); | |
| } else { | |
| return '?'; | |
| } | |
| return bind == STB_GLOBAL ? toupper(c) : c; | |
| } | |
| /* nm -n order: by address, then by name. */ | |
| static int compare_symbols(const void *a, const void *b) | |
| { | |
| const struct sysmap_symbol *sa = a, *sb = b; | |
| if (sa->addr != sb->addr) | |
| return sa->addr < sb->addr ? -1 : 1; | |
| return strcmp(sa->name, sb->name); | |
| } | |
| /* The symbols "nm -n | mksysmap" would list, in that order. */ | |
| static struct sysmap_symbol *elf_read_symbols(const struct elf_file *elf, | |
| size_t *nr_kept) | |
| { | |
| struct sysmap_symbol *syms = xmalloc(elf->nr_syms * sizeof(*syms)); | |
| size_t i, n = 0; | |
| for (i = 1; i < elf->nr_syms; i++) { | |
| Elf_Sym *sym = elf_symbol(elf, i); | |
| const char *name = elf->strtab + sym_name(sym); | |
| char type = elf_symbol_type(elf, sym); | |
| if (!type || sysmap_omits(name, type)) | |
| continue; | |
| syms[n].addr = sym_value(sym); | |
| syms[n].name = name; | |
| syms[n].type = type; | |
| n++; | |
| } | |
| qsort(syms, n, sizeof(*syms), compare_symbols); | |
| *nr_kept = n; | |
| return syms; | |
| } | |
| struct sysmap *sysmap_read(const char *path) | |
| { | |
| struct sysmap *map = xcalloc(1, sizeof(*map)); | |
| /* The names point into the mapping; keep it until the map is freed. */ | |
| if (elf_open_ro(path, (1 << ET_EXEC) | (1 << ET_DYN), &map->elf)) | |
| exit(EXIT_FAILURE); | |
| map->syms = elf_read_symbols(&map->elf, &map->nr_syms); | |
| map->addr_width = elf_map_long_size(map->elf.base) * 2; | |
| return map; | |
| } | |
| void sysmap_write(const struct sysmap *map, FILE *out) | |
| { | |
| size_t i; | |
| for (i = 0; i < map->nr_syms; i++) { | |
| const struct sysmap_symbol *s = &map->syms[i]; | |
| fprintf(out, "%0*llx %c %s\n", map->addr_width, s->addr, s->type, | |
| s->name); | |
| } | |
| } | |
| void sysmap_free(struct sysmap *map) | |
| { | |
| free(map->syms); | |
| elf_close(&map->elf); | |
| free(map); | |
| } |