diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-23 13:28:57 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-23 13:28:57 -0700 |
| commit | 4352b8aee98005853aa63f57d6377282de17a33f (patch) | |
| tree | 27614d7a9ef05c32d089004e104364ce42324837 /tools/objtool/klp-sympos.c | |
| download | linux-stable-4352b8aee98005853aa63f57d6377282de17a33f.tar.gz linux-stable-4352b8aee98005853aa63f57d6377282de17a33f.zip | |
Merge tag 'i3c/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linuxgrafted
Pull I3C updates from Alexandre Belloni:
"A new driver this cycle, for the AMD AXI I3C controller.
There are also new features such as IBI wakeup support, SETAASA device
discovery and ACPI support for the the DAA methods, meaning we can now
communicate with the SPD devices on DDR5 modules.
New driver:
- AMD AXI I3C master controller
Subsystem:
- I3C Common Command Code (CCC) handling improvements, especially
around GET CCCs
- SETAASA device discovery support
- ACPI support for all existing DAA methods like SETDASA, SETNEWDA as
well as I2C devices on I3C bus
- IBI-based wakeup support
Drivers:
- dw: SETAASA support
- mipi-i3c-hci: advertise IBI wakeup capability, AMD_PT I3C
controller support, PIO queue management support for HCI v1.2
- renesas: improve suspend to RAM support, add runtime PM support"
* tag 'i3c/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux: (70 commits)
i3c: dw: reduce do_daa time if there's no client
i3c: mipi-i3c-hci: Add support for AMD_PT I3C controller
i3c: mipi-i3c-hci: Add PIO queue management support for HCI v1.2
i3c: mipi-i3c-hci: Fix missing STAT_IBI_STATUS_THLD in PIO mode
i3c: dw: rename "pclk" to "apb" to match dt-binding
i3c: dw: make struct dw_i3c_cmd smaller
i3c: dw: use COMMAND_PORT_TRANSFER_ARG instead of hardcoding
i3c: renesas: Don't register devices when ENTDAA times out
i3c: master: dw-i3c-master: fix OD timing for first broadcast
i3c: mipi-i3c-hci: Advertise IBI wakeup capability
i3c: mipi-i3c-hci: Factor out i3c_hci_sysdev()
i3c: mipi-i3c-hci-pci: Propagate I3C wakeup requirements to PCI
i3c: master: Reject IBI requests from non-IBI-capable devices
i3c: master: Add helper to query bus wakeup requirements
i3c: master: Report wakeup events for IBIs
i3c: master: Support IBI-based wakeup capability
i3c: master: Fix potential UAF in i3c_device_match()
i3c: master: Fix potential UAF in i3c_device_uevent()
i3c: Make dev->desc locking assumptions explicit
i3c: master: Fix use-after-free of master->this
...
Diffstat (limited to 'tools/objtool/klp-sympos.c')
| -rw-r--r-- | tools/objtool/klp-sympos.c | 421 |
1 files changed, 421 insertions, 0 deletions
diff --git a/tools/objtool/klp-sympos.c b/tools/objtool/klp-sympos.c new file mode 100644 index 000000000..dfca9dd74 --- /dev/null +++ b/tools/objtool/klp-sympos.c @@ -0,0 +1,421 @@ +// 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; +} |
