summaryrefslogtreecommitdiff
path: root/tools/objtool/klp-diff.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/objtool/klp-diff.c')
-rw-r--r--tools/objtool/klp-diff.c272
1 files changed, 177 insertions, 95 deletions
diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c
index f8787d7d1454..16681a76f13d 100644
--- a/tools/objtool/klp-diff.c
+++ b/tools/objtool/klp-diff.c
@@ -12,7 +12,7 @@
#include <objtool/arch.h>
#include <objtool/klp.h>
#include <objtool/util.h>
-#include <arch/special.h>
+#include <objtool/special.h>
#include <linux/align.h>
#include <linux/objtool_types.h>
@@ -30,7 +30,9 @@ struct elfs {
struct export {
struct hlist_node hash;
- char *mod, *sym;
+ char *mod;
+ char *sym;
+ bool mod_ns;
};
bool debug, debug_correlate, debug_clone;
@@ -83,11 +85,40 @@ static char *escape_str(const char *orig)
return new;
}
+/*
+ * Convert a build-tree object path to a runtime module name: strip
+ * directory components, replace '-' with '_', and remove file
+ * extensions. Examples:
+ *
+ * "arch/x86/kvm/kvm" -> "kvm"
+ * "arch/x86/kvm/kvm-intel" -> "kvm_intel".
+ *
+ * Used by read_exports() to normalize Module.symvers entries and by
+ * __find_modname() as a fallback when .modinfo lacks a "name=" tag.
+ */
+static char *normalize_modname(char *name)
+{
+ char *slash = strrchr(name, '/');
+
+ if (slash)
+ name = slash + 1;
+
+ for (char *c = name; *c; c++) {
+ if (*c == '-')
+ *c = '_';
+ else if (*c == '.') {
+ *c = '\0';
+ break;
+ }
+ }
+ return name;
+}
+
static int read_exports(void)
{
const char *symvers = "Module.symvers";
char line[1024], *path = NULL;
- unsigned int line_num = 1;
+ unsigned int line_num = 0;
FILE *file;
file = fopen(symvers, "r");
@@ -106,9 +137,11 @@ static int read_exports(void)
}
while (fgets(line, 1024, file)) {
- char *sym, *mod, *type;
+ char *sym, *mod, *type, *namespace;
struct export *export;
+ line_num++;
+
sym = strchr(line, '\t');
if (!sym) {
ERROR("malformed Module.symvers (sym) at line %d", line_num);
@@ -133,6 +166,14 @@ static int read_exports(void)
*type++ = '\0';
+ namespace = strchr(type, '\t');
+ if (!namespace) {
+ ERROR("malformed Module.symvers (namespace) at line %d", line_num);
+ return -1;
+ }
+
+ *namespace++ = '\0';
+
if (*sym == '\0' || *mod == '\0') {
ERROR("malformed Module.symvers at line %d", line_num);
return -1;
@@ -150,12 +191,18 @@ static int read_exports(void)
return -1;
}
+ if (strcmp(export->mod, "vmlinux"))
+ export->mod = normalize_modname(export->mod);
+
export->sym = strdup(sym);
if (!export->sym) {
ERROR_GLIBC("strdup");
return -1;
}
+ /* EXPORT_SYMBOL_FOR_MODULES() */
+ export->mod_ns = strstarts(namespace, "module:");
+
hash_add(exports, &export->hash, str_hash(sym));
}
@@ -866,65 +913,6 @@ static int correlate_symbols(struct elfs *e)
return 0;
}
-/* "sympos" is used by livepatch to disambiguate duplicate symbol names */
-static unsigned long find_sympos(struct elf *elf, struct symbol *sym)
-{
- bool vmlinux = str_ends_with(objname, "vmlinux.o");
- unsigned long sympos = 0, nr_matches = 0;
- bool has_dup = false;
- struct symbol *s;
-
- if (sym->bind != STB_LOCAL)
- return 0;
-
- if (vmlinux && is_func_sym(sym)) {
- /*
- * HACK: Unfortunately, symbol ordering can differ between
- * vmlinux.o and vmlinux due to the linker script emitting
- * .text.unlikely* before .text*. Count .text.unlikely* first.
- *
- * TODO: Disambiguate symbols more reliably (checksums?)
- */
- for_each_sym(elf, s) {
- if (strstarts(s->sec->name, ".text.unlikely") &&
- !strcmp(s->name, sym->name)) {
- nr_matches++;
- if (s == sym)
- sympos = nr_matches;
- else
- has_dup = true;
- }
- }
- for_each_sym(elf, s) {
- if (!strstarts(s->sec->name, ".text.unlikely") &&
- !strcmp(s->name, sym->name)) {
- nr_matches++;
- if (s == sym)
- sympos = nr_matches;
- else
- has_dup = true;
- }
- }
- } else {
- 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;
-}
-
static int clone_sym_relocs(struct elfs *e, struct symbol *patched_sym);
static struct symbol *__clone_symbol(struct elf *elf, struct symbol *patched_sym,
@@ -1129,6 +1117,9 @@ static struct export *find_export(struct symbol *sym)
{
struct export *export;
+ if (is_local_sym(sym))
+ return NULL;
+
hash_for_each_possible(exports, export, hash, str_hash(sym->name)) {
if (!strcmp(export->sym, sym->name))
return export;
@@ -1158,18 +1149,7 @@ static const char *__find_modname(struct elfs *e)
return NULL;
}
- for (char *c = name; *c; c++) {
- if (*c == '/')
- name = c + 1;
- else if (*c == '-')
- *c = '_';
- else if (*c == '.') {
- *c = '\0';
- break;
- }
- }
-
- return name;
+ return normalize_modname(name);
}
/* Get the object's module name as defined by the kernel (and klp_object) */
@@ -1210,11 +1190,16 @@ static bool klp_reloc_needed(struct reloc *patched_reloc)
* clusterfunk that is late module patching, the patch module is
* allowed to be loaded before any modules it depends on.
*
- * If exported by vmlinux, a normal reloc will do.
+ * If exported by vmlinux to all modules, a normal reloc will do.
*/
export = find_export(patched_sym);
- if (export)
- return strcmp(export->mod, "vmlinux");
+ if (export) {
+ if (strcmp(export->mod, "vmlinux"))
+ return true;
+
+ /* EXPORT_SYMBOL_FOR_MODULES() gets a klp reloc */
+ return export->mod_ns;
+ }
if (!patched_sym->twin) {
/*
@@ -1331,39 +1316,79 @@ static int convert_reloc_sym(struct elf *elf, struct reloc *reloc)
}
/*
+ * Check if the original module already has a dependency on dep_mod, i.e. it
+ * already references at least one export from that module.
+ */
+static bool has_module_dep(struct elfs *e, const char *dep_mod)
+{
+ struct symbol *sym;
+
+ for_each_sym(e->orig, sym) {
+ struct export *exp;
+
+ if (!is_undef_sym(sym) || is_weak_sym(sym))
+ continue;
+
+ exp = find_export(sym);
+ if (exp && !strcmp(exp->mod, dep_mod))
+ return true;
+ }
+
+ return false;
+}
+
+/*
* Convert a regular relocation to a klp relocation (sort of).
*/
static int clone_reloc_klp(struct elfs *e, struct reloc *patched_reloc,
struct section *sec, unsigned long offset,
struct export *export)
{
+ const char *sym_modname, *sym_orig_name, *sec_objname;
struct symbol *patched_sym = patched_reloc->sym;
s64 addend = reloc_addend(patched_reloc);
- const char *sym_modname, *sym_orig_name;
- static struct section *klp_relocs;
+ char tombstone_name[SYM_NAME_LEN];
struct symbol *sym, *klp_sym;
unsigned long klp_reloc_off;
+ struct section *klp_relocs;
+ char sec_name[SEC_NAME_LEN];
char sym_name[SYM_NAME_LEN];
struct klp_reloc klp_reloc;
unsigned long sympos;
if (!patched_sym->twin) {
- ERROR("unexpected klp reloc for new symbol %s", patched_sym->name);
- return -1;
+ if (!export) {
+ ERROR("unexpected klp reloc for new symbol %s", patched_sym->name);
+ return -1;
+ }
+
+ if (strcmp(export->mod, "vmlinux") &&
+ !has_module_dep(e, export->mod)) {
+ ERROR("%s: new reference to %s (exported by %s) would create an undeclared module dependency",
+ patched_sym->name, export->sym, export->mod);
+ return -1;
+ }
}
/*
* Keep the original reloc intact for now to avoid breaking objtool run
* which relies on proper relocations for many of its features. This
- * will be disabled later by "objtool klp post-link".
+ * reloc now targets a functionally dead tombstone symbol and will be
+ * disabled later by "objtool klp post-link".
*
- * Convert it to UNDEF (and WEAK to avoid modpost warnings).
+ * Convert the symbol to UNDEF/WEAK and rename to
+ * .klp.tombstone.sym_name to prevent modpost from printing warnings or
+ * creating false module dependencies. The prefix is hidden from the
+ * objtool run itself by read_symbols().
*/
sym = patched_sym->clone;
if (!sym) {
- /* STB_WEAK: avoid modpost undefined symbol warnings */
- sym = elf_create_symbol(e->out, patched_sym->name, NULL,
+ if (snprintf_check(tombstone_name, SYM_NAME_LEN,
+ KLP_TOMBSTONE_PREFIX "%s", patched_sym->name))
+ return -1;
+
+ sym = elf_create_symbol(e->out, tombstone_name, NULL,
STB_WEAK, patched_sym->type, 0, 0);
if (!sym)
return -1;
@@ -1389,7 +1414,7 @@ static int clone_reloc_klp(struct elfs *e, struct reloc *patched_reloc,
return -1;
sym_orig_name = patched_sym->twin->name;
- sympos = find_sympos(e->orig, patched_sym->twin);
+ sympos = klp_find_sympos(e->orig, patched_sym->twin);
if (sympos == ULONG_MAX)
return -1;
}
@@ -1411,16 +1436,35 @@ static int clone_reloc_klp(struct elfs *e, struct reloc *patched_reloc,
}
/*
- * Create the __klp_relocs entry. This will be converted to an actual
- * KLP rela by "objtool klp post-link".
+ * Create the __klp_relocs.<objname> entry. This will be converted to
+ * an actual KLP rela by "objtool klp post-link".
*
* This intermediate step is necessary to prevent corruption by the
* linker, which doesn't know how to properly handle two rela sections
* applying to the same base section.
+ *
+ * The objname decides when the reloc gets applied. A reference to a
+ * vmlinux symbol goes in the vmlinux section so it gets applied when
+ * the patch module loads. Everything else goes in the patched
+ * object's section, applied when the patched module is loaded.
*/
+ if (!strcmp(sym_modname, "vmlinux")) {
+ sec_objname = "vmlinux";
+ } else {
+ sec_objname = find_modname(e);
+ if (!sec_objname)
+ return -1;
+ }
+
+ /* section format: __klp_relocs.objname */
+ if (snprintf_check(sec_name, SEC_NAME_LEN,
+ KLP_RELOCS_SEC ".%s", sec_objname))
+ return -1;
+
+ klp_relocs = find_section_by_name(e->out, sec_name);
if (!klp_relocs) {
- klp_relocs = elf_create_section(e->out, KLP_RELOCS_SEC, 0,
+ klp_relocs = elf_create_section(e->out, sec_name, 0,
0, SHT_PROGBITS, 8, SHF_ALLOC);
if (!klp_relocs)
return -1;
@@ -1568,6 +1612,10 @@ static int clone_sym_relocs(struct elfs *e, struct symbol *patched_sym)
!strcmp(patched_reloc->sym->sec->name, ".altinstr_aux"))
continue;
+ if (arch_alt_ignore_new_reloc(patched_sym->sec,
+ reloc_offset(patched_reloc)))
+ continue;
+
ret = convert_reloc_sym(e->patched, patched_reloc);
if (ret < 0) {
ERROR_FUNC(patched_rsec->base, reloc_offset(patched_reloc),
@@ -1591,6 +1639,7 @@ static int create_fake_symbol(struct elf *elf, struct section *sec,
unsigned long offset, size_t size)
{
char name[SYM_NAME_LEN];
+ struct symbol *sym;
unsigned int type;
static int ctr;
char *c;
@@ -1607,7 +1656,24 @@ static int create_fake_symbol(struct elf *elf, struct section *sec,
* while still allowing objdump to disassemble it.
*/
type = is_text_sec(sec) ? STT_NOTYPE : STT_OBJECT;
- return elf_create_symbol(elf, name, sec, STB_LOCAL, type, offset, size) ? 0 : -1;
+
+ sym = elf_create_symbol(elf, name, sec, STB_LOCAL, type, offset, size);
+ if (!sym)
+ return -1;
+
+ sym->fake = 1;
+ return 0;
+}
+
+static bool has_fake_symbols(struct section *sec)
+{
+ struct symbol *sym;
+
+ sec_for_each_sym(sec, sym)
+ if (sym->fake)
+ return true;
+
+ return false;
}
/*
@@ -1658,13 +1724,17 @@ static int create_fake_symbols(struct elf *elf)
for_each_reloc(sec->rsec, reloc) {
unsigned long offset, size;
struct reloc *next_reloc;
+ bool last = true;
if (annotype(elf, sec, reloc) != ANNOTYPE_DATA_SPECIAL)
continue;
offset = reloc_addend(reloc);
- size = 0;
+ /*
+ * Find the start of the next entry so the fake symbol size can
+ * be calculated.
+ */
next_reloc = reloc;
for_each_reloc_continue(sec->rsec, next_reloc) {
if (annotype(elf, sec, next_reloc) != ANNOTYPE_DATA_SPECIAL ||
@@ -1672,10 +1742,15 @@ static int create_fake_symbols(struct elf *elf)
continue;
size = reloc_addend(next_reloc) - offset;
+ last = false;
break;
}
- if (!size)
+ /*
+ * If no next entry found, this is the last entry, so its size
+ * is from the current offset to the end of the section.
+ */
+ if (last)
size = sec_size(reloc->sym->sec) - offset;
if (create_fake_symbol(elf, reloc->sym->sec, offset, size))
@@ -1690,7 +1765,11 @@ entsize:
unsigned int entry_size;
unsigned long offset;
- if (!is_special_section(sec) || find_symbol_by_offset(sec, 0))
+ if (!is_special_section(sec))
+ continue;
+
+ /* Skip sections already handled by step 1 above */
+ if (has_fake_symbols(sec))
continue;
if (!sec->rsec) {
@@ -2007,7 +2086,7 @@ static int create_klp_sections(struct elfs *e)
/* klp_func_ext.sympos */
BUILD_BUG_ON(sizeof(sympos) != sizeof_field(struct klp_func_ext, sympos));
- sympos = find_sympos(e->orig, sym->clone->twin);
+ sympos = klp_find_sympos(e->orig, sym->clone->twin);
if (sympos == ULONG_MAX)
return -1;
memcpy(func_data + offsetof(struct klp_func_ext, sympos), &sympos,
@@ -2161,6 +2240,9 @@ int cmd_klp_diff(int argc, const char **argv)
if (!e.orig || !e.patched)
return -1;
+ if (klp_sympos_init(e.orig))
+ return -1;
+
if (read_exports())
return -1;