[RFC PATCH v2 10/11] objtool/klp: Check unmatched data references
From: Longjun Luo
Date: Wed Nov 04 2026 - 08:35:20 EST
An unmatched data reference can still refer to existing state. Treating
it as new can give old and patched code separate copies of that state.
For ordinary data, allow new storage only when no original identity
candidate exists. For compound literals, reuse a unique unchanged object
in both functions' references. Reject ambiguous or changed compounds;
without a source identity, additions cannot be distinguished from changes.
Report the referencing function and offset for unresolved ordinary data.
Exclude fake special-section symbols created by objtool after correlation.
Validated the available objtool tests with GCC 16.2.1 and Clang
22.1.8. Only local-to-global-flip, sympos and sympos-vmlinux fail,
reflecting the linkage and duplicate-identity policies.
Assisted-by: LLM
Signed-off-by: Longjun Luo <luolongjuna@xxxxxxxxx>
---
tools/objtool/klp-diff.c | 134 +++++++++++++++++++++++++++++++++++++++
1 file changed, 134 insertions(+)
diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c
index e8252e586260..19f5161420cd 100644
--- a/tools/objtool/klp-diff.c
+++ b/tools/objtool/klp-diff.c
@@ -1426,6 +1426,135 @@ static int extend_twins_by_relocations(struct elfs *e)
return 0;
}
+static int find_unique_compound_reference(struct elf *elf, struct reloc *relocs,
+ unsigned int nr, struct symbol *sym,
+ struct symbol **match)
+{
+ unsigned int i;
+
+ *match = NULL;
+ for (i = 0; i < nr; i++) {
+ struct reloc *reloc = &relocs[i];
+ struct symbol *target;
+
+ if (reloc_type(reloc) == R_NONE || convert_reloc_secsym_to_sym(elf, reloc))
+ continue;
+ target = reloc->sym;
+ if (!is_compound_constant(target) || target->len != sym->len ||
+ target->csum.checksum != sym->csum.checksum)
+ continue;
+ if (*match && *match != target) {
+ ERROR("unresolved compound reference: %s matches %s[%u] and %s[%u]",
+ sym->name, (*match)->name, (*match)->idx, target->name, target->idx);
+ return -1;
+ }
+ *match = target;
+ }
+ return 0;
+}
+
+static int resolve_compound_reference(struct elfs *e, struct symbol *sym,
+ struct reloc *orig_relocs, unsigned int nr_orig,
+ struct reloc *patched_relocs, unsigned int nr_patched)
+{
+ struct symbol *orig_sym, *patched_sym;
+
+ if (!sym->csum.checksum) {
+ ERROR("%s: missing compound checksum", sym->name);
+ return -1;
+ }
+
+ if (find_unique_compound_reference(e->orig, orig_relocs, nr_orig, sym, &orig_sym))
+ return -1;
+ if (!orig_sym) {
+ /*
+ * Data changes are unsupported. With no source identity, new and
+ * changed compound literals cannot be distinguished here.
+ */
+ ERROR("unresolved compound reference: %s has no unchanged match", sym->name);
+ return -1;
+ }
+
+ if (find_unique_compound_reference(e->patched, patched_relocs, nr_patched,
+ sym, &patched_sym))
+ return -1;
+ if (patched_sym != sym) {
+ ERROR("unresolved compound reference: %s is not a unique reference in its function",
+ sym->name);
+ return -1;
+ }
+ if (orig_sym->twin) {
+ ERROR("compound twin conflict: %s[%u] paired with %s[%u]",
+ orig_sym->name, orig_sym->idx,
+ orig_sym->twin->name, orig_sym->twin->idx);
+ return -1;
+ }
+ sym->twin = orig_sym;
+ orig_sym->twin = sym;
+ dbg_correlate("%s(): %s[%u] -> %s[%u]", __func__,
+ orig_sym->name, orig_sym->idx, sym->name, sym->idx);
+ return 0;
+}
+
+/*
+ * Check references to symbols without twins for ambiguity:
+ *
+ * 1. Ordinary data: allow as new only if no original identity candidates exist.
+ * We assume DWARF identity information is stable across builds with the
+ * same compiler.
+ * 2. Compound literals (anonymous data with static storage duration):
+ * - delete: allowed.
+ * - same: require a unique unchanged match in both functions' references.
+ * - change: unsupported, to keep old and patched code's data views consistent.
+ * - new: reject for now; it cannot be distinguished from change.
+ * Supporting additions requires an explicit mechanism.
+ */
+static int check_unmatched_reference(struct elfs *e, struct reloc *reloc)
+{
+ struct symbol *patched_sym = reloc->sym, *orig_sym, *func;
+ struct reloc *orig_relocs = NULL, *patched_relocs = NULL;
+ unsigned int nr_orig = 0, nr_patched = 0;
+ u32 key;
+
+ if (reference_twin(patched_sym) || patched_sym->dont_correlate ||
+ !is_object_sym(patched_sym) || is_undef_sym(patched_sym) || !patched_sym->len)
+ return 0;
+
+ if (!is_compound_constant(patched_sym)) {
+ if (!has_dwarf_identity(patched_sym)) {
+ ERROR_FUNC(reloc->sec->base, reloc_offset(reloc),
+ "unresolved data reference: %s has no DWARF identity",
+ patched_sym->name);
+ return -1;
+ }
+ key = identity_hash(&patched_sym->identity);
+ /*
+ * With one old count.N and two patched candidates, the unmatched one
+ * may still refer to old state, even if the original already has a twin.
+ */
+ elf_hash_for_each_possible(e->orig, symbol_identity, orig_sym, identity_hash, key) {
+ if (!same_identity(&patched_sym->identity, &orig_sym->identity))
+ continue;
+ ERROR_FUNC(reloc->sec->base, reloc_offset(reloc),
+ "unresolved data reference: %s (%s, %s, %s), old candidate %s[%u]",
+ patched_sym->name, patched_sym->identity.source,
+ patched_sym->identity.scope,
+ patched_sym->identity.demangled_name,
+ orig_sym->name, orig_sym->idx);
+ return -1;
+ }
+ return 0;
+ }
+
+ func = find_func_containing(reloc->sec->base, reloc_offset(reloc));
+ if (func && func->included && func->twin && !same_function_checksum(func)) {
+ orig_relocs = get_func_relocs(e->orig, func->twin, &nr_orig);
+ patched_relocs = get_func_relocs(e->patched, func, &nr_patched);
+ }
+ return resolve_compound_reference(e, patched_sym, orig_relocs, nr_orig,
+ patched_relocs, nr_patched);
+}
+
/*
* Check if the original module already has a dependency on dep_mod, i.e. it
* already references at least one export from that module.
@@ -1629,6 +1758,9 @@ static int clone_reloc(struct elfs *e, struct reloc *patched_reloc,
bool klp;
/* Reject ambiguous data references before treating them as new. */
+ if (check_unmatched_reference(e, patched_reloc))
+ return -1;
+
klp = klp_reloc_needed(patched_reloc);
dbg_clone_reloc(sec, offset, patched_sym, addend, export, klp);
@@ -1781,6 +1913,8 @@ static int create_fake_symbol(struct elf *elf, struct section *sec,
return -1;
sym->fake = 1;
+ /* Special-section entries are copied with the patched code. */
+ sym->dont_correlate = 1;
return 0;
}
--
2.55.0