[PATCH 46/48] objtool/klp: Make function prefix handling more generic

From: Josh Poimboeuf

Date: Thu Apr 23 2026 - 00:24:29 EST


The way x86 klp-diff handles function prefix symbols is a bit awkward,
Also, x86 is the only arch which needs the __pfx_/cfi_ prefix symbols,
so this approach isn't extensible to other arches. And while other
arches *do* use __patchable_function_entries (PFEs), they use them in
completely different ways.

In preparation for supporting other arches, use a more generic approach
that will work for all arches with prefixed areas and/or PFE sections.

Signed-off-by: Josh Poimboeuf <jpoimboe@xxxxxxxxxx>
---
tools/objtool/include/objtool/elf.h | 15 +--
tools/objtool/klp-diff.c | 187 ++++++++++++++++++++++++----
2 files changed, 161 insertions(+), 41 deletions(-)

diff --git a/tools/objtool/include/objtool/elf.h b/tools/objtool/include/objtool/elf.h
index ba13dd67cf26..21441bd72971 100644
--- a/tools/objtool/include/objtool/elf.h
+++ b/tools/objtool/include/objtool/elf.h
@@ -119,6 +119,7 @@ struct elf {
struct list_head sections;
struct list_head symbols;
unsigned long num_relocs;
+ int pfe_offset;

int symbol_bits;
int symbol_name_bits;
@@ -532,20 +533,6 @@ static inline void set_sym_next_reloc(struct reloc *reloc, struct reloc *next)
reloc && reloc_offset(reloc) < sym->offset + sym->len; \
reloc = rsec_next_reloc(sym->sec->rsec, reloc))

-static inline struct symbol *get_func_prefix(struct symbol *func)
-{
- struct symbol *prev;
-
- if (!is_func_sym(func))
- return NULL;
-
- prev = sec_prev_sym(func);
- if (prev && is_prefix_func(prev))
- return prev;
-
- return NULL;
-}
-
#define OFFSET_STRIDE_BITS 4
#define OFFSET_STRIDE (1UL << OFFSET_STRIDE_BITS)
#define OFFSET_STRIDE_MASK (~(OFFSET_STRIDE - 1))
diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c
index acb76aefd04f..420d05633aba 100644
--- a/tools/objtool/klp-diff.c
+++ b/tools/objtool/klp-diff.c
@@ -213,6 +213,88 @@ static int read_sym_checksums(struct elf *elf)
return 0;
}

+/*
+ * Detect the offset from the function entry point to its
+ * __patchable_function_entries (PFE) relocation target.
+ *
+ * offset < 0 (before function entry):
+ *
+ * CONFIG_FINEIBT (x86)
+ * CONFIG_MITIGATION_CALL_DEPTH_TRACKING (x86)
+ */
+static int read_pfe_offset(struct elf *elf)
+{
+ bool has_pfe = false;
+ struct section *sec;
+
+ for_each_sec(elf, sec) {
+ struct reloc *reloc;
+
+ if (strcmp(sec->name, "__patchable_function_entries"))
+ continue;
+ if (!sec->rsec)
+ continue;
+
+ has_pfe = true;
+
+ for_each_reloc(sec->rsec, reloc) {
+ unsigned long target = reloc->sym->offset + reloc_addend(reloc);
+ struct symbol *func;
+
+ func = find_func_containing(reloc->sym->sec, target);
+ if (func) {
+ if (is_prefix_func(func))
+ elf->pfe_offset = target - (func->offset + func->len);
+ else
+ elf->pfe_offset = target - func->offset;
+ return 0;
+ }
+ }
+ }
+
+ if (has_pfe) {
+ ERROR("can't find __patchable_function_entries offset");
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * Detect the size of the area before a function's entry point. This prefix
+ * area is used for CFI type hashes, call thunks, or ftrace call ops.
+ *
+ * __pfx_ prefix function (x86):
+ *
+ * CONFIG_MITIGATION_CALL_DEPTH_TRACKING
+ *
+ * __cfi_ prefix function (x86):
+ *
+ * CONFIG_CFI
+ */
+static unsigned long func_pfx_size(struct elf *elf, struct symbol *func)
+{
+ struct symbol *pfx;
+
+ /* x86 __pfx_ and/or __cfi_ */
+ if (func->offset) {
+ pfx = find_func_containing(func->sec, func->offset - 1);
+ if (pfx && pfx->prefix) {
+ struct symbol *pfx2;
+
+ /* FineIBT has both */
+ if (pfx->offset) {
+ pfx2 = find_func_containing(func->sec, pfx->offset - 1);
+ if (pfx2 && pfx2->prefix)
+ pfx = pfx2;
+ }
+
+ return func->offset - pfx->offset;
+ }
+ }
+ return 0;
+}
+
static struct symbol *first_file_symbol(struct elf *elf)
{
struct symbol *sym;
@@ -302,6 +384,7 @@ static bool is_special_section(struct section *sec)
"__ex_table",
"__jump_table",
"__mcount_loc",
+ "__patchable_function_entries",

/*
* Extract .static_call_sites here to inherit non-module
@@ -872,7 +955,7 @@ static struct symbol *__clone_symbol(struct elf *elf, struct symbol *patched_sym
bool data_too)
{
struct section *out_sec = NULL;
- unsigned long offset = 0;
+ unsigned long offset = 0, pfx_size = 0;
struct symbol *out_sym;

if (data_too && !is_undef_sym(patched_sym)) {
@@ -901,20 +984,26 @@ static struct symbol *__clone_symbol(struct elf *elf, struct symbol *patched_sym
offset = ALIGN(sec_size(out_sec), out_sec->sh.sh_addralign);

if (patched_sym->len || is_sec_sym(patched_sym)) {
- void *data = NULL;
size_t size;
+ void *data = NULL;
+
+ /* Clone function prefix area */
+ if (is_func_sym(patched_sym))
+ pfx_size = func_pfx_size(elf, patched_sym);

/* bss doesn't have data */
if (patched_sym->sec->data && patched_sym->sec->data->d_buf)
- data = patched_sym->sec->data->d_buf + patched_sym->offset;
+ data = patched_sym->sec->data->d_buf + patched_sym->offset - pfx_size;

if (is_sec_sym(patched_sym))
size = sec_size(patched_sym->sec);
else
- size = patched_sym->len;
+ size = patched_sym->len + pfx_size;

if (!elf_add_data(elf, out_sec, data, size))
return NULL;
+
+ offset += pfx_size;
}
}

@@ -924,6 +1013,23 @@ static struct symbol *__clone_symbol(struct elf *elf, struct symbol *patched_sym
if (!out_sym)
return NULL;

+ /*
+ * The copied prefixed area may have had a __cfi_ symbol which needs to
+ * be copied. During the module link, objtool collates these in a
+ * .cfi_sites section for FineIBT.
+ */
+ if (pfx_size && is_func_sym(patched_sym)) {
+ struct symbol *cfi_sym;
+
+ cfi_sym = find_func_containing(patched_sym->sec, patched_sym->offset - pfx_size);
+ if (cfi_sym && strstarts(cfi_sym->name, "__cfi_")) {
+ if (!elf_create_symbol(elf, cfi_sym->name, out_sec,
+ cfi_sym->bind, cfi_sym->type,
+ offset - pfx_size, cfi_sym->len))
+ return NULL;
+ }
+ }
+
sym_created:
patched_sym->clone = out_sym;
out_sym->clone = patched_sym;
@@ -960,20 +1066,11 @@ static const char *sym_bind(struct symbol *sym)
static struct symbol *clone_symbol(struct elfs *e, struct symbol *patched_sym,
bool data_too)
{
- struct symbol *pfx;
-
if (patched_sym->clone)
return patched_sym->clone;

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) {
- pfx = get_func_prefix(patched_sym);
- if (pfx)
- clone_symbol(e, pfx, true);
- }
-
if (!__clone_symbol(e->out, patched_sym, data_too))
return NULL;

@@ -985,15 +1082,8 @@ static struct symbol *clone_symbol(struct elfs *e, struct symbol *patched_sym,

static void mark_included_function(struct symbol *func)
{
- struct symbol *pfx;
-
func->included = 1;

- /* Include prefix function */
- pfx = get_func_prefix(func);
- if (pfx)
- pfx->included = 1;
-
/* Make sure .cold parent+child always stay together */
if (func->cfunc && func->cfunc != func)
func->cfunc->included = 1;
@@ -1222,17 +1312,37 @@ static int convert_reloc_sym_to_secsym(struct elf *elf, struct reloc *reloc)
return 0;
}

+/*
+ * __patchable_function_entries relocs point to the patchable entry NOPs,
+ * which are at 'pfe_offset' bytes from the function symbol.
+ *
+ * Some entries (e.g., removed weak functions, syscall -ENOSYS stubs) don't
+ * have a corresponding function symbol. Skip those with a return value of 1.
+ */
+static int convert_pfe_reloc(struct elf *elf, struct reloc *reloc)
+{
+ struct symbol *func;
+
+ func = find_func_by_offset(reloc->sym->sec,
+ reloc->sym->offset +
+ reloc_addend(reloc) - elf->pfe_offset);
+ if (!func)
+ return 1;
+
+ reloc->sym = func;
+ set_reloc_sym(elf, reloc, func->idx);
+ set_reloc_addend(elf, reloc, elf->pfe_offset);
+ return 0;
+}
+
static int convert_reloc_secsym_to_sym(struct elf *elf, struct reloc *reloc)
{
struct symbol *sym = reloc->sym;
struct section *sec = sym->sec;

- /* 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;
+ if (!strcmp(reloc->sec->name, ".rela__patchable_function_entries"))
+ return convert_pfe_reloc(elf, reloc);

- /* No dedicated section; find the symbol manually */
sym = find_symbol_containing(sec, arch_adjusted_addend(reloc));
if (!sym) {
/*
@@ -1249,7 +1359,6 @@ static int convert_reloc_secsym_to_sym(struct elf *elf, struct reloc *reloc)
return -1;
}

-found_sym:
reloc->sym = sym;
set_reloc_sym(elf, reloc, sym->idx);
set_reloc_addend(elf, reloc, reloc_addend(reloc) - sym->offset);
@@ -1802,6 +1911,9 @@ static int validate_special_section_klp_reloc(struct elfs *e, struct symbol *sym

static int clone_special_section(struct elfs *e, struct section *patched_sec)
{
+ bool is_pfe = !strcmp(patched_sec->name, "__patchable_function_entries");
+ struct section *out_sec = NULL;
+ struct reloc *patched_reloc;
struct symbol *patched_sym;

/*
@@ -1809,6 +1921,7 @@ static int clone_special_section(struct elfs *e, struct section *patched_sec)
* reference included functions.
*/
sec_for_each_sym(patched_sec, patched_sym) {
+ struct symbol *out_sym;
int ret;

if (!is_object_sym(patched_sym))
@@ -1823,8 +1936,23 @@ static int clone_special_section(struct elfs *e, struct section *patched_sec)
if (ret > 0)
continue;

- if (!clone_symbol(e, patched_sym, true))
+ out_sym = clone_symbol(e, patched_sym, true);
+ if (!out_sym)
return -1;
+
+ if (!is_pfe || (out_sec && out_sec->sh.sh_link))
+ continue;
+
+ /*
+ * For reasons, the patched object has multiple PFE sections,
+ * but we only need to create one combined section for the
+ * output. Link the single PFE ouput section to a random text
+ * section to satisfy the linker for SHF_LINK_ORDER.
+ */
+ out_sec = out_sym->sec;
+ patched_reloc = find_reloc_by_dest(e->patched, patched_sec,
+ patched_sym->offset);
+ out_sec->sh.sh_link = patched_reloc->sym->clone->sec->idx;
}

return 0;
@@ -2121,6 +2249,9 @@ int cmd_klp_diff(int argc, const char **argv)
if (read_sym_checksums(e.patched))
return -1;

+ if (read_pfe_offset(e.patched))
+ return -1;
+
if (correlate_symbols(&e))
return -1;

@@ -2134,6 +2265,8 @@ int cmd_klp_diff(int argc, const char **argv)
if (!e.out)
return -1;

+ e.out->pfe_offset = e.patched->pfe_offset;
+
/*
* Special section fake symbols are needed so that individual special
* section entries can be extracted by clone_special_sections().
--
2.53.0