blob: a482968eba5112360ac040443f84b13c05904f02 [file]
// 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);
}