421 lines · 9.3 KB · C
RawDownload
// 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;}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t