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 <[email protected]>
---
 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


Reply via email to