Something went wrong. Try again.
(I'm so sorry!) Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422// SPDX-License-Identifier: GPL-2.0-or-later/* * Compute "sympos", the position used by livepatch to disambiguate * duplicate symbol names in the patched object. */#include <stdlib.h>#include <string.h>#include <fcntl.h>
#include <objtool/objtool.h>#include <objtool/warn.h>#include <objtool/endianness.h>#include <objtool/klp.h>
#include <linux/string.h>
struct vmlinux_sym { struct hlist_node hash; const char *name; u64 addr;};
struct vmlinux_symid { struct hlist_node hash; u64 id; u64 addr;};
struct vmlinux_o_symid { struct hlist_node hash; u64 id; unsigned int sym_idx;};
static DEFINE_HASHTABLE(vmlinux_o_symids, 16);
/* * The original linked kernel, found next to the orig vmlinux.o. Read with raw * libelf rather than elf_open_read(): only the symbol table and the resolved * .klp.symid table are needed, not the (huge) instruction/reloc machinery. * * Both tables are built once by read_orig_vmlinux(). The Elf handle stays * open because the hashed names point into its mmapped string table. */static struct { Elf *elf; DECLARE_HASHTABLE(syms, 16); /* name -> address */ DECLARE_HASHTABLE(symids, 16); /* .klp.symid id -> address */} vmlinux;
/* * Would the symbol be visible to the runtime's kallsyms-based symbol lookup? */static bool vmlinux_sym_in_kallsyms(Elf *elf, GElf_Sym *sym){ unsigned int type = GELF_ST_TYPE(sym->st_info); GElf_Shdr shdr; Elf_Scn *scn;
if (sym->st_shndx == SHN_UNDEF || sym->st_shndx >= SHN_LORESERVE) return false;
if (type == STT_SECTION || type == STT_FILE) return false;
scn = elf_getscn(elf, sym->st_shndx); if (!scn || !gelf_getshdr(scn, &shdr)) return false;
return shdr.sh_flags & SHF_ALLOC;}
static int read_orig_vmlinux(const char *filename){ size_t shstrndx, nr_syms = 0, nr_symids = 0, strtab_idx = 0; Elf_Data *symtab_data = NULL, *symid_data = NULL; struct klp_symid *symids; Elf_Scn *scn = NULL; GElf_Ehdr ehdr; int fd;
fd = open(filename, O_RDONLY); if (fd == -1) { ERROR_GLIBC("can't open '%s'", filename); return -1; }
if (elf_version(EV_CURRENT) == EV_NONE) { ERROR_ELF("elf_version"); return -1; }
vmlinux.elf = elf_begin(fd, ELF_C_READ_MMAP, NULL); if (!vmlinux.elf) { ERROR_ELF("elf_begin"); return -1; }
if (!gelf_getehdr(vmlinux.elf, &ehdr)) { ERROR_ELF("gelf_getehdr"); return -1; }
if (elf_getshdrstrndx(vmlinux.elf, &shstrndx)) { ERROR_ELF("elf_getshdrstrndx"); return -1; }
while ((scn = elf_nextscn(vmlinux.elf, scn))) { const char *name; GElf_Shdr shdr;
if (!gelf_getshdr(scn, &shdr)) { ERROR_ELF("gelf_getshdr"); return -1; }
if (shdr.sh_type == SHT_SYMTAB) { symtab_data = elf_getdata(scn, NULL); if (!symtab_data) { ERROR_ELF("elf_getdata"); return -1; } nr_syms = shdr.sh_size / shdr.sh_entsize; strtab_idx = shdr.sh_link; continue; }
name = elf_strptr(vmlinux.elf, shstrndx, shdr.sh_name); if (name && !strcmp(name, KLP_SYMID_SEC)) { if (shdr.sh_size % sizeof(struct klp_symid)) { ERROR("%s: %s: struct klp_symid size mismatch", filename, KLP_SYMID_SEC); return -1; } symid_data = elf_getdata(scn, NULL); if (!symid_data) { ERROR_ELF("elf_getdata"); return -1; } nr_symids = shdr.sh_size / sizeof(struct klp_symid); } }
if (!symtab_data) { ERROR("%s: missing symbol table", filename); return -1; }
if (!symid_data) { ERROR("%s: missing %s section, kernel not built with CONFIG_KLP_BUILD?", filename, KLP_SYMID_SEC); return -1; }
for (size_t i = 0; i < nr_syms; i++) { struct vmlinux_sym *vsym; const char *name; GElf_Sym s;
if (!gelf_getsym(symtab_data, i, &s)) { ERROR_ELF("gelf_getsym"); return -1; }
if (!vmlinux_sym_in_kallsyms(vmlinux.elf, &s)) continue;
name = elf_strptr(vmlinux.elf, strtab_idx, s.st_name); if (!name) continue;
vsym = calloc(1, sizeof(*vsym)); if (!vsym) { ERROR_GLIBC("calloc"); return -1; }
vsym->name = name; vsym->addr = s.st_value; hash_add(vmlinux.syms, &vsym->hash, str_hash(name)); }
symids = symid_data->d_buf;
for (size_t i = 0; i < nr_symids; i++) { struct vmlinux_symid *vsymid;
vsymid = calloc(1, sizeof(*vsymid)); if (!vsymid) { ERROR_GLIBC("calloc"); return -1; }
vsymid->id = __bswap_if_needed(&ehdr, symids[i].id); vsymid->addr = __bswap_if_needed(&ehdr, symids[i].addr); hash_add(vmlinux.symids, &vsymid->hash, vsymid->id); }
/* the fd and Elf handle stay open, the hashed names live in the mmap */ return 0;}
/* * Read the orig vmlinux.o's .klp.symid table, an array of entries whose 'addr' * fields have relocs to the symbols they describe. */static int read_vmlinux_o_symids(struct elf *vmlinux_o){ struct section *sec;
for_each_sec(vmlinux_o, sec) { unsigned long nr;
if (strcmp(sec->name, KLP_SYMID_SEC)) continue;
if (sec_size(sec) % sizeof(struct klp_symid)) { ERROR("%s: %s: struct klp_symid size mismatch", vmlinux_o->name, KLP_SYMID_SEC); return -1; }
nr = sec_size(sec) / sizeof(struct klp_symid);
for (unsigned long i = 0; i < nr; i++) { unsigned long offset = i * sizeof(struct klp_symid); struct vmlinux_o_symid *entry; struct klp_symid *symid; struct reloc *reloc;
entry = calloc(1, sizeof(*entry)); if (!entry) { ERROR_GLIBC("calloc"); return -1; }
symid = sec->data->d_buf + offset; entry->id = bswap_if_needed(vmlinux_o, symid->id);
reloc = find_reloc_by_dest(vmlinux_o, sec, offset + offsetof(struct klp_symid, addr)); if (!reloc) { ERROR("%s: missing reloc for %s entry", vmlinux_o->name, KLP_SYMID_SEC); return -1; } entry->sym_idx = reloc->sym->idx;
hash_add(vmlinux_o_symids, &entry->hash, entry->sym_idx); } }
return 0;}
int klp_sympos_init(struct elf *orig){ char *filename; int ret;
if (!str_ends_with(objname, "vmlinux.o")) return 0;
if (read_vmlinux_o_symids(orig)) return -1;
filename = strndup(objname, strlen(objname) - 2); if (!filename) { ERROR_GLIBC("strndup"); return -1; }
ret = read_orig_vmlinux(filename); free(filename);
return ret;}
/* Find the symbol's id in the orig vmlinux.o's .klp.symid table */static int find_vmlinux_o_symid(struct symbol *sym, u64 *id){ struct vmlinux_o_symid *entry;
hash_for_each_possible(vmlinux_o_symids, entry, hash, sym->idx) { if (entry->sym_idx == sym->idx) { *id = entry->id; return 0; } }
ERROR("no %s entry for symbol %s in orig vmlinux.o", KLP_SYMID_SEC, sym->name); return -1;}
/* Find the symbol's final address in the orig vmlinux's .klp.symid table */static int find_vmlinux_symid_addr(u64 id, u64 *addr){ struct vmlinux_symid *symid;
hash_for_each_possible(vmlinux.symids, symid, hash, id) { if (symid->id == id) { *addr = symid->addr; return 0; } }
return -1;}
/* * Find the sympos of a vmlinux-local symbol by ranking its final address * among the duplicately named symbols in the linked orig vmlinux, replicating * the order in which kallsyms_on_each_match_symbol() counts them. */static unsigned long find_vmlinux_sympos(struct symbol *sym){ unsigned long nr_matches = 0, sympos = 1; u32 key = str_hash(sym->name); struct vmlinux_sym *vsym; bool found = false; u64 id, addr;
hash_for_each_possible(vmlinux.syms, vsym, hash, key) if (!strcmp(vsym->name, sym->name)) nr_matches++;
if (!nr_matches) { ERROR("can't find symbol %s in orig vmlinux", sym->name); return ULONG_MAX; }
/* * Unique symbols don't need disambiguating. They also have no * .klp.symid entry, which is only emitted for names duplicated in * vmlinux.o, so the lookups below would fail. */ if (nr_matches == 1) return 0;
if (find_vmlinux_o_symid(sym, &id)) return ULONG_MAX;
if (find_vmlinux_symid_addr(id, &addr)) { ERROR("no %s entry for symbol %s in orig vmlinux", KLP_SYMID_SEC, sym->name); return ULONG_MAX; }
hash_for_each_possible(vmlinux.syms, vsym, hash, key) { if (strcmp(vsym->name, sym->name)) continue;
if (vsym->addr < addr) sympos++; else if (vsym->addr == addr) found = true; }
if (!found) { ERROR("%s address mismatch for symbol %s, stale orig vmlinux?", KLP_SYMID_SEC, sym->name); return ULONG_MAX; }
return sympos;}
static bool is_init_sym(struct symbol *sym){ return strstarts(sym->sec->name, ".init");}
/* * "sympos" is used by livepatch to disambiguate duplicate symbol names. */unsigned long klp_find_sympos(struct elf *elf, struct symbol *sym){ unsigned long sympos = 0, nr_matches = 0; bool has_dup = false; struct symbol *s;
if (is_init_sym(sym)) { ERROR("%s: can't patch or reference init code/data", sym->name); return ULONG_MAX; }
if (sym->bind != STB_LOCAL) return 0;
/* * vmlinux: the final link reorders symbols relative to vmlinux.o, * so the position needs to be derived from the linked orig vmlinux via * the .klp.symid table. */ if (vmlinux.elf) return find_vmlinux_sympos(sym);
/* * modules: the final .ko preserves symbol table order, so a * symtab-order count here matches the runtime count done by * module_kallsyms_on_each_symbol(). */ for_each_sym(elf, s) { if (!strcmp(s->name, sym->name)) { nr_matches++; if (s == sym) sympos = nr_matches; else has_dup = true; } }
if (!sympos) { ERROR("can't find sympos for %s", sym->name); return ULONG_MAX; }
return has_dup ? sympos : 0;}