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.c796
1 files changed, 574 insertions, 222 deletions
diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c
index 0b0d1503851f..f8787d7d1454 100644
--- a/tools/objtool/klp-diff.c
+++ b/tools/objtool/klp-diff.c
@@ -33,6 +33,9 @@ struct export {
char *mod, *sym;
};
+bool debug, debug_correlate, debug_clone;
+int indent;
+
static const char * const klp_diff_usage[] = {
"objtool klp diff [<options>] <in1.o> <in2.o> <out.o>",
NULL,
@@ -40,17 +43,14 @@ static const char * const klp_diff_usage[] = {
static const struct option klp_diff_options[] = {
OPT_GROUP("Options:"),
- OPT_BOOLEAN('d', "debug", &debug, "enable debug output"),
+ OPT_BOOLEAN('d', "debug", &debug, "enable all debug output"),
+ OPT_BOOLEAN(0, "debug-correlate", &debug_correlate, "enable correlation debug output"),
+ OPT_BOOLEAN(0, "debug-clone", &debug_clone, "enable cloning debug output"),
OPT_END(),
};
static DEFINE_HASHTABLE(exports, 15);
-static inline u32 str_hash(const char *str)
-{
- return jhash(str, strlen(str), 0);
-}
-
static char *escape_str(const char *orig)
{
size_t len = 0;
@@ -171,7 +171,7 @@ static int read_sym_checksums(struct elf *elf)
sec = find_section_by_name(elf, ".discard.sym_checksum");
if (!sec) {
- ERROR("'%s' missing .discard.sym_checksum section, file not processed by 'objtool --checksum'?",
+ ERROR("'%s' missing .discard.sym_checksum section, file not processed by 'objtool klp checksum'?",
elf->name);
return -1;
}
@@ -206,7 +206,7 @@ static int read_sym_checksums(struct elf *elf)
return -1;
}
- if (is_func_sym(sym))
+ if (is_func_sym(sym) || is_object_sym(sym))
sym->csum.checksum = sym_checksum->checksum;
}
@@ -242,25 +242,39 @@ static struct symbol *next_file_symbol(struct elf *elf, struct symbol *sym)
static bool is_uncorrelated_static_local(struct symbol *sym)
{
static const char * const vars[] = {
- "__already_done.",
- "__func__.",
- "__key.",
- "__warned.",
- "_entry.",
- "_entry_ptr.",
- "_rs.",
- "descriptor.",
- "CSWTCH.",
+ "__already_done",
+ "__func__",
+ "__key",
+ "__warned",
+ "_entry",
+ "_entry_ptr",
+ "_rs",
+ "descriptor",
+ "CSWTCH",
};
+ const char *dot;
if (!is_object_sym(sym) || !is_local_sym(sym))
return false;
- if (!strcmp(sym->sec->name, ".data.once"))
+ /* WARN_ONCE, etc */
+ if (!strcmp(sym->sec->name, ".data..once"))
return true;
+ dot = strchr(sym->name, '.');
+ if (!dot)
+ return false;
+
for (int i = 0; i < ARRAY_SIZE(vars); i++) {
- if (strstarts(sym->name, vars[i]))
+ size_t len = strlen(vars[i]);
+
+ /* GCC: <var>.<id> */
+ if (strstarts(sym->name, vars[i]) && (sym->name[len] == '.'))
+ return true;
+
+ /* Clang: <func>.<var>[.<id>] */
+ if (strstarts(dot + 1, vars[i]) &&
+ (dot[1 + len] == '.' || dot[1 + len] == '\0'))
return true;
}
@@ -268,20 +282,21 @@ static bool is_uncorrelated_static_local(struct symbol *sym)
}
/*
- * Clang emits several useless .Ltmp_* code labels.
+ * .L symbols are assembler-local labels not present in kallsyms. They must
+ * never become KLP relocations; instead their data is cloned into the patch
+ * module. This covers .Ltmp* (Clang temp labels), .L__const.* (Clang local
+ * constants), and any other assembler-local pattern.
*/
-static bool is_clang_tmp_label(struct symbol *sym)
+static bool is_local_label(struct symbol *sym)
{
- return is_notype_sym(sym) &&
- is_text_sec(sym->sec) &&
- strstarts(sym->name, ".Ltmp") &&
- isdigit(sym->name[5]);
+ return strstarts(sym->name, ".L");
}
static bool is_special_section(struct section *sec)
{
static const char * const specials[] = {
".altinstructions",
+ ".kcfi_traps",
".smp_locks",
"__bug_table",
"__ex_table",
@@ -339,6 +354,45 @@ static bool is_special_section_aux(struct section *sec)
}
/*
+ * Symbols created by ___ADDRESSABLE() are only used to convince the toolchain
+ * not to optimize out the referenced symbol.
+ */
+static bool is_addressable_sym(struct symbol *sym)
+{
+ return !strcmp(sym->sec->name, ".discard.addressable");
+}
+
+/*
+ * ABS symbols are typically assembly .set/.equ constants which are never
+ * referenced by relocations. (Exclude FILE symbols which are also SHN_ABS.)
+ */
+static bool is_abs_sym(struct symbol *sym)
+{
+ return sym->sym.st_shndx == SHN_ABS && !is_file_sym(sym);
+}
+
+static bool is_initcall_sym(struct symbol *sym)
+{
+ return strstarts(sym->name, "__initcall__") ||
+ strstarts(sym->name, "__initstub__");
+}
+
+/*
+ * Some .rodata is anonymous and can't be correlated due to there being no
+ * symbol names.
+ *
+ * The .rodata.cst* sections aren't technically anonymous, they're SHF_MERGE
+ * constant pool sections containing small fixed-size data (lookup tables,
+ * bitmasks) which are only read by value, so pointer equivalence isn't needed.
+ * They are typically referenced by UBSAN data sections.
+ */
+static bool is_anonymous_rodata(struct symbol *sym)
+{
+ return is_rodata_sec(sym->sec) &&
+ (!is_object_sym(sym) || strstarts(sym->sec->name, ".rodata.cst"));
+}
+
+/*
* These symbols should never be correlated, so their local patched versions
* are used instead of linking to the originals.
*/
@@ -347,96 +401,391 @@ static bool dont_correlate(struct symbol *sym)
return is_file_sym(sym) ||
is_null_sym(sym) ||
is_sec_sym(sym) ||
+ is_abs_sym(sym) ||
is_prefix_func(sym) ||
is_uncorrelated_static_local(sym) ||
- is_clang_tmp_label(sym) ||
+ is_local_label(sym) ||
is_string_sec(sym->sec) ||
+ is_anonymous_rodata(sym) ||
+ is_initcall_sym(sym) ||
+ is_addressable_sym(sym) ||
is_special_section(sym->sec) ||
- is_special_section_aux(sym->sec) ||
- strstarts(sym->name, "__initcall__");
+ is_special_section_aux(sym->sec);
}
-struct process_demangled_name_data {
- struct symbol *ret;
- int count;
-};
+static const char *llvm_suffix(const char *name)
+{
+ return strstr(name, ".llvm.");
+}
+
+static bool is_llvm_sym(struct symbol *sym)
+{
+ return llvm_suffix(sym->name);
+}
-static void process_demangled_name(struct symbol *sym, void *d)
+/*
+ * Determine if two symbols have compatible source file origins:
+ *
+ * - If both symbols are local, only return true if they belong to the same
+ * ELF file symbol.
+ *
+ * - If both symbols are global, always return true, as globals don't have
+ * file associations.
+ *
+ * - If they have different scopes, also return true, as the patch might have
+ * changed the symbol's scope.
+ *
+ * Works for both same-ELF (direct pointer compare) and cross-ELF
+ * (compare via file->twin) cases.
+ */
+static bool maybe_same_file(struct symbol *sym1, struct symbol *sym2)
{
- struct process_demangled_name_data *data = d;
+ if (!sym1->file || !sym2->file)
+ return true;
+ if (sym1->file == sym2->file)
+ return true;
+ return sym1->file->twin == sym2->file;
+}
- if (sym->twin)
- return;
+/*
+ * Similar to maybe_same_file(), but strict: no scope changes allowed.
+ *
+ * Works for both same-ELF (direct pointer compare) and cross-ELF
+ * (compare via file->twin) cases.
+ */
+static bool same_file(struct symbol *sym1, struct symbol *sym2)
+{
+ if (llvm_suffix(sym1->name) && llvm_suffix(sym2->name))
+ return true;
+ if (!sym1->file && !sym2->file)
+ return true;
+ if (!sym1->file || !sym2->file)
+ return false;
+ if (sym1->file == sym2->file)
+ return true;
+ return sym1->file->twin == sym2->file;
+}
- data->count++;
- data->ret = sym;
+/*
+ * Is it a local symbol, or at least was it local in the translation unit
+ * before LLVM promoted it?
+ */
+static bool is_tu_local_sym(struct symbol *sym)
+{
+ return is_local_sym(sym) || is_llvm_sym(sym);
}
/*
- * When there is no full name match, try match demangled_name. This would
- * match original foo.llvm.123 to patched foo.llvm.456.
+ * Try to find sym1's twin in patched using deterministic matching.
+ *
+ * Multiple symbols can share a demangled name (e.g., static functions in
+ * different TUs). This function counts same-named candidates through a
+ * funnel of progressively tighter filters. Each level is a strict subset
+ * of the previous one.
+ *
+ * The widest level that yields a 1:1 match wins. Narrower levels are only
+ * needed when the wider level is ambiguous (count > 1).
+ *
+ * Candidates are pre-filtered by maybe_same_file(), which narrows most
+ * local symbols to their own TU. For example, 19 different static
+ * type_show() functions across vmlinux.o each see only one candidate after
+ * pre-filtering, so they match immediately at Level 1.
+ *
+ * Level 1 (name): Works when the demangled name is unique after
+ * pre-filtering. Handles most symbols: unique globals like copy_signal(),
+ * or per-TU locals like pcspkr_probe().
+ *
+ * Level 2 (scope): Filters by local-vs-global (TU-local-vs-not). Example:
+ * parse_header() exists as both a static and a global function. Level 1
+ * sees both (same demangled name), but Level 2 separates them by scope.
*
- * Note that, in very rare cases, it is possible to have multiple
- * foo.llvm.<hash> in the same kernel. When this happens, report error and
- * fail the diff.
+ * Level 3 (file): Strict file matching via same_file(), which rejects scope
+ * changes. Example: LLVM-promoted foo.llvm.12345 (global, no FILE symbol)
+ * vs genuine local foo (has FILE symbol). Both are TU-local so Level 2
+ * can't distinguish them, but same_file() rejects the pair because one has
+ * a file association and the other doesn't.
+ *
+ * Level 4 (checksum): Distinguishes by function checksum. Example:
+ * usb_devnode.llvm.AAA and usb_devnode.llvm.BBB are two LLVM-promoted
+ * functions from different TUs with the same demangled name. After a TU
+ * change, the .llvm. hashes change but the functions themselves may be
+ * unchanged. Level 4 matches each to the patched candidate with the
+ * same checksum.
*/
-static int find_global_symbol_by_demangled_name(struct elf *elf, struct symbol *sym,
- struct symbol **out_sym)
+static struct symbol *find_twin(struct elfs *e, struct symbol *sym1)
{
- struct process_demangled_name_data data = {};
+ struct symbol *name_last = NULL, *scope_last = NULL,
+ *file_last = NULL, *csum_last = NULL;
+ unsigned int name_orig = 0, name_patched = 0;
+ unsigned int scope_orig = 0, scope_patched = 0;
+ unsigned int file_orig = 0, file_patched = 0;
+ unsigned int csum_orig = 0, csum_patched = 0;
+ struct symbol *sym2, *match = NULL;
+
+ /* Count orig candidates */
+ for_each_sym_by_demangled_name(e->orig, sym1->demangled_name, sym2) {
+ if (sym2->twin || sym1->type != sym2->type || sym2->dont_correlate ||
+ (!maybe_same_file(sym1, sym2)))
+ continue;
- iterate_global_symbol_by_demangled_name(elf, sym->demangled_name,
- process_demangled_name,
- &data);
- if (data.count > 1) {
- ERROR("Multiple (%d) correlation candidates for %s", data.count, sym->name);
- return -1;
+ /* Level 1: name match (widest filter) */
+ name_orig++;
+
+ /* Level 2: scope (scope changes allowed) */
+ if (is_tu_local_sym(sym1) != is_tu_local_sym(sym2))
+ continue;
+ scope_orig++;
+
+ /* Level 3: file (scope changes disallowed) */
+ if (!same_file(sym1, sym2))
+ continue;
+ file_orig++;
+
+ /* Level 4: checksum (unchanged symbols) */
+ if (sym1->len != sym2->len || !sym1->csum.checksum ||
+ sym1->csum.checksum != sym2->csum.checksum)
+ continue;
+ csum_orig++;
}
- *out_sym = data.ret;
- return 0;
+
+ /* Count patched candidates */
+ for_each_sym_by_demangled_name(e->patched, sym1->demangled_name, sym2) {
+ if (sym2->twin || sym1->type != sym2->type || sym2->dont_correlate ||
+ !maybe_same_file(sym1, sym2))
+ continue;
+
+ /* Level 1 */
+ name_patched++;
+ name_last = sym2;
+
+ /* Level 2 */
+ if (is_tu_local_sym(sym1) != is_tu_local_sym(sym2))
+ continue;
+ scope_patched++;
+ scope_last = sym2;
+
+ /* Level 3 */
+ if (!same_file(sym1, sym2))
+ continue;
+ file_patched++;
+ file_last = sym2;
+
+ /* Level 4 */
+ if (sym1->len != sym2->len || !sym1->csum.checksum ||
+ sym1->csum.checksum != sym2->csum.checksum)
+ continue;
+ csum_patched++;
+ csum_last = sym2;
+ }
+
+ /* Return the widest level that yields a unique (1:1) match */
+ if (name_orig == 1 && name_patched == 1)
+ match = name_last;
+ else if (scope_orig == 1 && scope_patched == 1)
+ match = scope_last;
+ else if (file_orig == 1 && file_patched == 1)
+ match = file_last;
+ else if (csum_orig == 1 && csum_patched == 1)
+ match = csum_last;
+
+ if (!match)
+ return NULL;
+
+ if (name_orig != 1 || name_patched != 1)
+ dbg_correlate("find_twin(): %s%s -> %s%s",
+ sym1->name, is_func_sym(sym1) ? "()" : "",
+ match->name, is_func_sym(match) ? "()" : "");
+
+ return match;
}
+struct llvm_suffix_pair {
+ struct hlist_node hash;
+ const char *orig;
+ const char *patched;
+};
+
+static DECLARE_HASHTABLE(suffix_map, 7);
+
/*
- * For each symbol in the original kernel, find its corresponding "twin" in the
- * patched kernel.
+ * Build a mapping of known orig-to-patched LLVM suffixes based on
+ * already-correlated symbol pairs. All promoted symbols from the same TU
+ * share the same .llvm.<hash> suffix, so one correlated pair seeds the map
+ * for the entire TU.
*/
-static int correlate_symbols(struct elfs *e)
+static int update_suffix_map(struct elf *elf)
{
- struct symbol *file1_sym, *file2_sym;
- struct symbol *sym1, *sym2;
+ struct llvm_suffix_pair *entry;
+ struct symbol *sym;
- file1_sym = first_file_symbol(e->orig);
- file2_sym = first_file_symbol(e->patched);
+ for_each_sym(elf, sym) {
+ const char *s1, *s2;
+ bool found;
- /*
- * Correlate any locals before the first FILE symbol. This has been
- * seen when LTO inexplicably strips the initramfs_data.o FILE symbol
- * due to the file only containing data and no code.
- */
- for_each_sym(e->orig, sym1) {
- if (sym1 == file1_sym || !is_local_sym(sym1))
- break;
+ if (!sym->twin)
+ continue;
- if (dont_correlate(sym1))
+ s1 = llvm_suffix(sym->name);
+ s2 = llvm_suffix(sym->twin->name);
+
+ if (!s1 || !s2)
continue;
- for_each_sym(e->patched, sym2) {
- if (sym2 == file2_sym || !is_local_sym(sym2))
+ found = false;
+ hash_for_each_possible(suffix_map, entry, hash, str_hash(s1)) {
+ if (!strcmp(entry->orig, s1)) {
+ found = true;
break;
+ }
+ }
+ if (found)
+ continue;
- if (sym2->twin || dont_correlate(sym2))
- continue;
+ entry = calloc(1, sizeof(*entry));
+ if (!entry) {
+ ERROR_GLIBC("calloc");
+ return -1;
+ }
- if (strcmp(sym1->demangled_name, sym2->demangled_name))
- continue;
+ entry->orig = s1;
+ entry->patched = s2;
+ hash_add(suffix_map, &entry->hash, str_hash(s1));
+ }
- sym1->twin = sym2;
- sym2->twin = sym1;
+ return 0;
+}
+
+/*
+ * Match by translating the symbol's .llvm.<hash> suffix through the suffix
+ * map to find the corresponding hash suffix for the patched object.
+ *
+ * Example: In the original kernel, TU drivers/base/core.c contains
+ * foo.llvm.12345 and bar.llvm.12345 (same TU, same hash). After patching,
+ * they become foo.llvm.67890 and bar.llvm.67890. If foo was already
+ * correlated by find_twin() (e.g., unique by name), the suffix map records
+ * .llvm.12345 -> .llvm.67890. When processing bar.llvm.12345, this
+ * function looks up .llvm.12345, gets .llvm.67890, constructs the name
+ * bar.llvm.67890, and finds the match.
+ */
+static struct symbol *find_twin_suffixed(struct elf *elf, struct symbol *sym1)
+{
+ const char *suffix, *patched_suffix = NULL;
+ struct symbol *sym2, *match = NULL;
+ char name[SYM_NAME_LEN];
+ struct llvm_suffix_pair *entry;
+ int count = 0;
+
+ suffix = llvm_suffix(sym1->name);
+ if (!suffix)
+ return NULL;
+
+ hash_for_each_possible(suffix_map, entry, hash, str_hash(suffix)) {
+ if (!strcmp(entry->orig, suffix)) {
+ patched_suffix = entry->patched;
break;
}
}
+ if (!patched_suffix)
+ return NULL;
+
+ if (snprintf_check(name, SYM_NAME_LEN, "%s%s",
+ sym1->demangled_name, patched_suffix))
+ return NULL;
+
+ for_each_sym_by_name(elf, name, sym2) {
+ if (sym2->twin || sym1->type != sym2->type || sym2->dont_correlate)
+ continue;
+ count++;
+ match = sym2;
+ }
+
+ if (count != 1)
+ return NULL;
+
+ dbg_correlate("find_suffixed_twin(): %s%s -> %s%s",
+ sym1->name, is_func_sym(sym1) ? "()" : "",
+ match->name, is_func_sym(match) ? "()" : "");
+
+ return match;
+}
+
+/*
+ * Last-resort positional matching.
+ *
+ * Finds a symbol with the same position in the symbol table among
+ * same-demangled-name candidates, similar to livepatch sympos. Note that
+ * LLVM-promoted symbols are globals, which come after locals in the symbol
+ * table, so we have to be careful not to compare different scopes.
+ *
+ * Example: arch/x86/events/intel/core.c defines many __quirk variables via
+ * X86_MATCH_*() macros. In the symbol table they appear as __quirk.90,
+ * __quirk.97, __quirk.101, etc., all with demangled name __quirk, same
+ * scope, and same FILE symbol. No deterministic filter can distinguish
+ * them, so they're matched by position: the 1st __quirk in orig matches the
+ * 1st in patched, the 2nd matches the 2nd, etc.
+ *
+ * This is less deterministic than the other strategies, so it's done last.
+ */
+static struct symbol *find_twin_positional(struct elfs *e, struct symbol *sym1)
+{
+ unsigned int idx_orig = 0, idx_patched = 0;
+ unsigned int sym1_pos = 0;
+ struct symbol *sym2, *match = NULL;
+
+ for_each_sym_by_demangled_name(e->orig, sym1->demangled_name, sym2) {
+ if (sym2->twin || sym1->type != sym2->type || sym2->dont_correlate ||
+ !maybe_same_file(sym1, sym2))
+ continue;
+ if (is_tu_local_sym(sym1) != is_tu_local_sym(sym2) ||
+ is_llvm_sym(sym1) != is_llvm_sym(sym2))
+ continue;
+ if (sym1 == sym2)
+ sym1_pos = idx_orig;
+ idx_orig++;
+ }
+
+ for_each_sym_by_demangled_name(e->patched, sym1->demangled_name, sym2) {
+ if (sym2->twin || sym1->type != sym2->type || sym2->dont_correlate ||
+ !maybe_same_file(sym1, sym2))
+ continue;
+ if (is_tu_local_sym(sym1) != is_tu_local_sym(sym2) ||
+ is_llvm_sym(sym1) != is_llvm_sym(sym2))
+ continue;
+ if (idx_patched == sym1_pos)
+ match = sym2;
+ idx_patched++;
+ }
+
+ if (idx_orig != idx_patched)
+ return NULL;
+
+ dbg_correlate("find_twin_positional(): %s%s -> %s%s",
+ sym1->name, is_func_sym(sym1) ? "()" : "",
+ match->name, is_func_sym(match) ? "()" : "");
+
+ return match;
+}
+
+/*
+ * Correlate symbols between the orig and patched objects. This is a
+ * prerequisite for detecting changed functions, as well as for properly
+ * translating relocations so they point to the correct symbol.
+ */
+static int correlate_symbols(struct elfs *e)
+{
+ struct symbol *file1_sym, *file2_sym;
+ struct symbol *sym1, *sym2;
+ bool progress;
+
+ for_each_sym(e->orig, sym1)
+ sym1->dont_correlate = dont_correlate(sym1);
+ for_each_sym(e->patched, sym2)
+ sym2->dont_correlate = dont_correlate(sym2);
+
+ /* Correlate FILE symbols */
+ file1_sym = first_file_symbol(e->orig);
+ file2_sym = first_file_symbol(e->patched);
- /* Correlate locals after the first FILE symbol */
for (; ; file1_sym = next_file_symbol(e->orig, file1_sym),
file2_sym = next_file_symbol(e->patched, file2_sym)) {
@@ -460,96 +809,56 @@ static int correlate_symbols(struct elfs *e)
file1_sym->twin = file2_sym;
file2_sym->twin = file1_sym;
-
- sym1 = file1_sym;
-
- for_each_sym_continue(e->orig, sym1) {
- if (is_file_sym(sym1) || !is_local_sym(sym1))
- break;
-
- if (dont_correlate(sym1))
- continue;
-
- sym2 = file2_sym;
- for_each_sym_continue(e->patched, sym2) {
- if (is_file_sym(sym2) || !is_local_sym(sym2))
- break;
-
- if (sym2->twin || dont_correlate(sym2))
- continue;
-
- if (strcmp(sym1->demangled_name, sym2->demangled_name))
- continue;
-
- sym1->twin = sym2;
- sym2->twin = sym1;
- break;
- }
- }
}
- /* Correlate globals */
- for_each_sym(e->orig, sym1) {
- if (sym1->bind == STB_LOCAL)
- continue;
-
- sym2 = find_global_symbol_by_name(e->patched, sym1->name);
- if (sym2 && !sym2->twin) {
- sym1->twin = sym2;
- sym2->twin = sym1;
- }
- }
/*
- * Correlate globals with demangled_name.
- * A separate loop is needed because we want to finish all the
- * full name correlations first.
+ * Correlate in two phases: loop deterministic levels until no more
+ * progress, then use positional fallback for the rest. This prevents
+ * the nondeterministic positional matching from stealing symbols that
+ * have deterministic matches.
*/
- for_each_sym(e->orig, sym1) {
- if (sym1->bind == STB_LOCAL || sym1->twin)
- continue;
-
- if (find_global_symbol_by_demangled_name(e->patched, sym1, &sym2))
- return -1;
-
- if (sym2 && !sym2->twin) {
+ hash_init(suffix_map);
+ do {
+ progress = false;
+ for_each_sym(e->orig, sym1) {
+ if (sym1->twin || sym1->dont_correlate)
+ continue;
+ sym2 = find_twin(e, sym1);
+ if (!sym2)
+ continue;
sym1->twin = sym2;
sym2->twin = sym1;
+ progress = true;
}
- }
- /* Correlate original locals with patched globals */
- for_each_sym(e->orig, sym1) {
- if (sym1->twin || dont_correlate(sym1) || !is_local_sym(sym1))
- continue;
-
- sym2 = find_global_symbol_by_name(e->patched, sym1->name);
- if (!sym2 && find_global_symbol_by_demangled_name(e->patched, sym1, &sym2))
+ if (update_suffix_map(e->orig))
return -1;
- if (sym2 && !sym2->twin) {
+ for_each_sym(e->orig, sym1) {
+ if (sym1->twin || sym1->dont_correlate)
+ continue;
+ sym2 = find_twin_suffixed(e->patched, sym1);
+ if (!sym2)
+ continue;
sym1->twin = sym2;
sym2->twin = sym1;
+ progress = true;
}
- }
+ } while (progress);
- /* Correlate original globals with patched locals */
- for_each_sym(e->patched, sym2) {
- if (sym2->twin || dont_correlate(sym2) || !is_local_sym(sym2))
+ for_each_sym(e->orig, sym1) {
+ if (sym1->twin || sym1->dont_correlate)
continue;
-
- sym1 = find_global_symbol_by_name(e->orig, sym2->name);
- if (!sym1 && find_global_symbol_by_demangled_name(e->orig, sym2, &sym1))
- return -1;
-
- if (sym1 && !sym1->twin) {
- sym2->twin = sym1;
- sym1->twin = sym2;
- }
+ sym2 = find_twin_positional(e, sym1);
+ if (!sym2)
+ continue;
+ sym1->twin = sym2;
+ sym2->twin = sym1;
}
for_each_sym(e->orig, sym1) {
- if (sym1->twin || dont_correlate(sym1))
+ if (sym1->twin || sym1->dont_correlate)
continue;
WARN("no correlation: %s", sym1->name);
}
@@ -655,7 +964,7 @@ static struct symbol *__clone_symbol(struct elf *elf, struct symbol *patched_sym
size_t size;
/* bss doesn't have data */
- if (patched_sym->sec->data->d_buf)
+ if (patched_sym->sec->data && patched_sym->sec->data->d_buf)
data = patched_sym->sec->data->d_buf + patched_sym->offset;
if (is_sec_sym(patched_sym))
@@ -715,7 +1024,7 @@ static struct symbol *clone_symbol(struct elfs *e, struct symbol *patched_sym,
if (patched_sym->clone)
return patched_sym->clone;
- dbg_indent("%s%s", patched_sym->name, data_too ? " [+DATA]" : "");
+ dbg_clone("%s%s", patched_sym->name, data_too ? " [+DATA]" : "");
/* Make sure the prefix gets cloned first */
if (is_func_sym(patched_sym) && data_too) {
@@ -757,19 +1066,24 @@ static void mark_included_function(struct symbol *func)
*/
static int mark_changed_functions(struct elfs *e)
{
- struct symbol *sym_orig, *patched_sym;
+ struct symbol *orig_sym, *patched_sym;
bool changed = false;
/* Find changed functions */
- for_each_sym(e->orig, sym_orig) {
- if (!is_func_sym(sym_orig) || is_prefix_func(sym_orig))
+ for_each_sym(e->orig, orig_sym) {
+ if (orig_sym->dont_correlate)
continue;
- patched_sym = sym_orig->twin;
+ patched_sym = orig_sym->twin;
if (!patched_sym)
continue;
- if (sym_orig->csum.checksum != patched_sym->csum.checksum) {
+ if (orig_sym->csum.checksum != patched_sym->csum.checksum) {
+ if (!is_func_sym(orig_sym)) {
+ ERROR("changed data: %s", orig_sym->name);
+ return -1;
+ }
+
patched_sym->changed = 1;
mark_included_function(patched_sym);
changed = true;
@@ -778,7 +1092,7 @@ static int mark_changed_functions(struct elfs *e)
/* Find added functions and print them */
for_each_sym(e->patched, patched_sym) {
- if (!is_func_sym(patched_sym) || is_prefix_func(patched_sym))
+ if (!is_func_sym(patched_sym) || patched_sym->dont_correlate)
continue;
if (!patched_sym->twin) {
@@ -794,7 +1108,7 @@ static int mark_changed_functions(struct elfs *e)
printf("%s: changed function: %s\n", objname, patched_sym->name);
}
- return !changed ? -1 : 0;
+ return !changed ? 1 : 0;
}
static int clone_included_functions(struct elfs *e)
@@ -811,39 +1125,6 @@ static int clone_included_functions(struct elfs *e)
return 0;
}
-/*
- * Determine whether a relocation should reference the section rather than the
- * underlying symbol.
- */
-static bool section_reference_needed(struct section *sec)
-{
- /*
- * String symbols are zero-length and uncorrelated. It's easier to
- * deal with them as section symbols.
- */
- if (is_string_sec(sec))
- return true;
-
- /*
- * .rodata has mostly anonymous data so there's no way to determine the
- * length of a needed reference. just copy the whole section if needed.
- */
- if (strstarts(sec->name, ".rodata"))
- return true;
-
- /* UBSAN anonymous data */
- if (strstarts(sec->name, ".data..Lubsan") || /* GCC */
- strstarts(sec->name, ".data..L__unnamed_")) /* Clang */
- return true;
-
- return false;
-}
-
-static bool is_reloc_allowed(struct reloc *reloc)
-{
- return section_reference_needed(reloc->sym->sec) == is_sec_sym(reloc->sym);
-}
-
static struct export *find_export(struct symbol *sym)
{
struct export *export;
@@ -917,7 +1198,7 @@ static bool klp_reloc_needed(struct reloc *patched_reloc)
struct export *export;
/* no external symbol to reference */
- if (dont_correlate(patched_sym))
+ if (patched_sym->dont_correlate)
return false;
/* For included functions, a regular reloc will do. */
@@ -952,34 +1233,60 @@ static bool klp_reloc_needed(struct reloc *patched_reloc)
return true;
}
+/* Return -1 error, 0 success, 1 skip */
static int convert_reloc_sym_to_secsym(struct elf *elf, struct reloc *reloc)
{
struct symbol *sym = reloc->sym;
struct section *sec = sym->sec;
+ if (is_sec_sym(sym))
+ return 0;
+
if (!sec->sym && !elf_create_section_symbol(elf, sec))
return -1;
reloc->sym = sec->sym;
- set_reloc_sym(elf, reloc, sym->idx);
+ set_reloc_sym(elf, reloc, sec->sym->idx);
set_reloc_addend(elf, reloc, sym->offset + reloc_addend(reloc));
return 0;
}
+/* Return -1 error, 0 success, 1 skip */
static int convert_reloc_secsym_to_sym(struct elf *elf, struct reloc *reloc)
{
struct symbol *sym = reloc->sym;
struct section *sec = sym->sec;
+ if (!is_sec_sym(sym))
+ return 0;
+
/* If the symbol has a dedicated section, it's easy to find */
sym = find_symbol_by_offset(sec, 0);
if (sym && sym->len == sec_size(sec))
goto found_sym;
/* No dedicated section; find the symbol manually */
- sym = find_symbol_containing(sec, arch_adjusted_addend(reloc));
+ sym = find_symbol_containing_inclusive(sec, arch_adjusted_addend(reloc));
if (!sym) {
/*
+ * This is presumably an .altinstr_replacement section which is
+ * empty due to it only having zero-length replacement(s).
+ */
+ if (!sec_size(sec))
+ return 1;
+
+ /*
+ * .rodata is a mixed bag of named objects and anonymous data.
+ *
+ * Convert section symbol references to named object symbols
+ * when possible, to preserve pointer identity for const
+ * structs like file_operations. Otherwise a section symbol is
+ * fine.
+ */
+ if (is_rodata_sec(sec))
+ return 0;
+
+ /*
* This can happen for special section references to weak code
* whose symbol has been stripped by the linker.
*/
@@ -994,18 +1301,33 @@ found_sym:
}
/*
+ * Sections with anonymous or uncorrelated data (strings, UBSAN data, Clang
+ * anonymous constants) need section symbol references.
+ */
+static bool is_uncorrelated_section(struct section *sec)
+{
+ return is_string_sec(sec) ||
+ strstarts(sec->name, ".data..Lubsan") || /* GCC */
+ strstarts(sec->name, ".data..L__unnamed_") || /* Clang */
+ strstarts(sec->name, ".data..Lanon."); /* Clang */
+}
+
+/*
* Convert a relocation symbol reference to the needed format: either a section
- * symbol or the underlying symbol itself.
+ * symbol or the underlying symbol itself. Return -1 error, 0 success, 1 skip.
*/
static int convert_reloc_sym(struct elf *elf, struct reloc *reloc)
{
- if (is_reloc_allowed(reloc))
- return 0;
+ struct section *sec = reloc->sym->sec;
- if (section_reference_needed(reloc->sym->sec))
+ if (reloc_type(reloc) == R_NONE)
+ return 1;
+
+ if (is_uncorrelated_section(sec))
return convert_reloc_sym_to_secsym(elf, reloc);
- else
- return convert_reloc_secsym_to_sym(elf, reloc);
+
+ /* Everything else: references should use named symbols. */
+ return convert_reloc_secsym_to_sym(elf, reloc);
}
/*
@@ -1079,7 +1401,7 @@ static int clone_reloc_klp(struct elfs *e, struct reloc *patched_reloc,
klp_sym = find_symbol_by_name(e->out, sym_name);
if (!klp_sym) {
- __dbg_indent("%s", sym_name);
+ __dbg_clone("%s", sym_name);
/* STB_WEAK: avoid modpost undefined symbol warnings */
klp_sym = elf_create_symbol(e->out, sym_name, NULL,
@@ -1130,7 +1452,7 @@ static int clone_reloc_klp(struct elfs *e, struct reloc *patched_reloc,
}
#define dbg_clone_reloc(sec, offset, patched_sym, addend, export, klp) \
- dbg_indent("%s+0x%lx: %s%s0x%lx [%s%s%s%s%s%s]", \
+ dbg_clone("%s+0x%lx: %s%s0x%lx [%s%s%s%s%s%s]", \
sec->name, offset, patched_sym->name, \
addend >= 0 ? "+" : "-", labs(addend), \
sym_type(patched_sym), \
@@ -1150,13 +1472,6 @@ static int clone_reloc(struct elfs *e, struct reloc *patched_reloc,
struct symbol *out_sym;
bool klp;
- if (!is_reloc_allowed(patched_reloc)) {
- ERROR_FUNC(patched_reloc->sec->base, reloc_offset(patched_reloc),
- "missing symbol for reference to %s+%ld",
- patched_sym->name, addend);
- return -1;
- }
-
klp = klp_reloc_needed(patched_reloc);
dbg_clone_reloc(sec, offset, patched_sym, addend, export, klp);
@@ -1186,13 +1501,13 @@ static int clone_reloc(struct elfs *e, struct reloc *patched_reloc,
/*
* For strings, all references use section symbols, thanks to
- * section_reference_needed(). clone_symbol() has cloned an empty
+ * convert_reloc_sym(). clone_symbol() has cloned an empty
* version of the string section. Now copy the string itself.
*/
if (is_string_sec(patched_sym->sec)) {
const char *str = patched_sym->sec->data->d_buf + addend;
- __dbg_indent("\"%s\"", escape_str(str));
+ __dbg_clone("\"%s\"", escape_str(str));
addend = elf_add_string(e->out, out_sym->sec, str);
if (addend == -1)
@@ -1239,6 +1554,7 @@ static int clone_sym_relocs(struct elfs *e, struct symbol *patched_sym)
for_each_reloc(patched_rsec, patched_reloc) {
unsigned long offset;
+ int ret;
if (reloc_offset(patched_reloc) < start ||
reloc_offset(patched_reloc) >= end)
@@ -1252,12 +1568,15 @@ static int clone_sym_relocs(struct elfs *e, struct symbol *patched_sym)
!strcmp(patched_reloc->sym->sec->name, ".altinstr_aux"))
continue;
- if (convert_reloc_sym(e->patched, patched_reloc)) {
+ ret = convert_reloc_sym(e->patched, patched_reloc);
+ if (ret < 0) {
ERROR_FUNC(patched_rsec->base, reloc_offset(patched_reloc),
"failed to convert reloc sym '%s' to its proper format",
patched_reloc->sym->name);
return -1;
}
+ if (ret > 0)
+ continue;
offset = out_sym->offset + (reloc_offset(patched_reloc) - patched_sym->offset);
@@ -1334,7 +1653,7 @@ static int create_fake_symbols(struct elf *elf)
sec = find_section_by_name(elf, ".discard.annotate_data");
if (!sec || !sec->rsec)
- return 0;
+ goto entsize;
for_each_reloc(sec->rsec, reloc) {
unsigned long offset, size;
@@ -1366,7 +1685,7 @@ static int create_fake_symbols(struct elf *elf)
/*
* 2) Make symbols for sh_entsize, and simple arrays of pointers:
*/
-
+entsize:
for_each_sec(elf, sec) {
unsigned int entry_size;
unsigned long offset;
@@ -1400,6 +1719,7 @@ static int create_fake_symbols(struct elf *elf)
/* Keep a special section entry if it references an included function */
static bool should_keep_special_sym(struct elf *elf, struct symbol *sym)
{
+ bool annotate_insn = !strcmp(sym->sec->name, ".discard.annotate_insn");
struct reloc *reloc;
if (is_sec_sym(sym) || !sym->sec->rsec)
@@ -1409,7 +1729,16 @@ static bool should_keep_special_sym(struct elf *elf, struct symbol *sym)
if (convert_reloc_sym(elf, reloc))
continue;
- if (is_func_sym(reloc->sym) && reloc->sym->included)
+ if (!reloc->sym->clone || is_undef_sym(reloc->sym->clone))
+ continue;
+
+ /*
+ * Keep special section references to cloned functions.
+ * In some cases annotate_insn can also reference cloned alt
+ * replacement fake symbols; keep those references as well.
+ */
+ if (is_func_sym(reloc->sym) ||
+ (annotate_insn && is_notype_sym(reloc->sym)))
return true;
}
@@ -1553,15 +1882,28 @@ static int clone_special_section(struct elfs *e, struct section *patched_sec)
/* Extract only the needed bits from special sections */
static int clone_special_sections(struct elfs *e)
{
- struct section *patched_sec;
+ struct section *sec, *annotate_insn = NULL;
- for_each_sec(e->patched, patched_sec) {
- if (is_special_section(patched_sec)) {
- if (clone_special_section(e, patched_sec))
+ for_each_sec(e->patched, sec) {
+ if (is_special_section(sec)) {
+ if (!strcmp(sec->name, ".discard.annotate_insn")) {
+ annotate_insn = sec;
+ continue;
+ }
+ if (clone_special_section(e, sec))
return -1;
}
}
+ /*
+ * Do .discard.annotate_insn last, it can reference other special
+ * sections (alt replacements) so they need to be cloned first.
+ */
+ if (annotate_insn) {
+ if (clone_special_section(e, annotate_insn))
+ return -1;
+ }
+
return 0;
}
@@ -1638,7 +1980,8 @@ static int create_klp_sections(struct elfs *e)
unsigned long sympos;
void *func_data;
- if (!is_func_sym(sym) || sym->cold || !sym->clone || !sym->clone->changed)
+ if (!is_func_sym(sym) || is_cold_func(sym) ||
+ !sym->clone || !sym->clone->changed)
continue;
/* allocate klp_func_ext */
@@ -1798,11 +2141,17 @@ static int copy_import_ns(struct elfs *e)
int cmd_klp_diff(int argc, const char **argv)
{
struct elfs e = {0};
+ int ret;
argc = parse_options(argc, argv, klp_diff_options, klp_diff_usage, 0);
if (argc != 3)
usage_with_options(klp_diff_usage, klp_diff_options);
+ if (debug) {
+ debug_correlate = true;
+ debug_clone = true;
+ }
+
objname = argv[0];
e.orig = elf_open_read(argv[0], O_RDONLY);
@@ -1824,7 +2173,10 @@ int cmd_klp_diff(int argc, const char **argv)
if (correlate_symbols(&e))
return -1;
- if (mark_changed_functions(&e))
+ ret = mark_changed_functions(&e);
+ if (ret < 0)
+ return -1;
+ if (ret > 0)
return 0;
e.out = elf_create_file(&e.orig->ehdr, argv[2]);