// 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 #include #include #include #include #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.", }; /* __*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_: 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); }