objtool, kcsan: Remove memory barrier instrumentation from noinstr
[platform/kernel/linux-rpi.git] / tools / objtool / check.c
index 06b5c16..a9a1f72 100644 (file)
@@ -5,6 +5,7 @@
 
 #include <string.h>
 #include <stdlib.h>
+#include <sys/mman.h>
 
 #include <arch/elf.h>
 #include <objtool/builtin.h>
@@ -26,7 +27,11 @@ struct alternative {
        bool skip_orig;
 };
 
-struct cfi_init_state initial_func_cfi;
+static unsigned long nr_cfi, nr_cfi_reused, nr_cfi_cache;
+
+static struct cfi_init_state initial_func_cfi;
+static struct cfi_state init_cfi;
+static struct cfi_state func_cfi;
 
 struct instruction *find_insn(struct objtool_file *file,
                              struct section *sec, unsigned long offset)
@@ -173,6 +178,7 @@ static bool __dead_end_function(struct objtool_file *file, struct symbol *func,
                "rewind_stack_do_exit",
                "kunit_try_catch_throw",
                "xen_start_kernel",
+               "cpu_bringup_and_idle",
        };
 
        if (!func)
@@ -265,6 +271,78 @@ static void init_insn_state(struct insn_state *state, struct section *sec)
                state->noinstr = sec->noinstr;
 }
 
+static struct cfi_state *cfi_alloc(void)
+{
+       struct cfi_state *cfi = calloc(sizeof(struct cfi_state), 1);
+       if (!cfi) {
+               WARN("calloc failed");
+               exit(1);
+       }
+       nr_cfi++;
+       return cfi;
+}
+
+static int cfi_bits;
+static struct hlist_head *cfi_hash;
+
+static inline bool cficmp(struct cfi_state *cfi1, struct cfi_state *cfi2)
+{
+       return memcmp((void *)cfi1 + sizeof(cfi1->hash),
+                     (void *)cfi2 + sizeof(cfi2->hash),
+                     sizeof(struct cfi_state) - sizeof(struct hlist_node));
+}
+
+static inline u32 cfi_key(struct cfi_state *cfi)
+{
+       return jhash((void *)cfi + sizeof(cfi->hash),
+                    sizeof(*cfi) - sizeof(cfi->hash), 0);
+}
+
+static struct cfi_state *cfi_hash_find_or_add(struct cfi_state *cfi)
+{
+       struct hlist_head *head = &cfi_hash[hash_min(cfi_key(cfi), cfi_bits)];
+       struct cfi_state *obj;
+
+       hlist_for_each_entry(obj, head, hash) {
+               if (!cficmp(cfi, obj)) {
+                       nr_cfi_cache++;
+                       return obj;
+               }
+       }
+
+       obj = cfi_alloc();
+       *obj = *cfi;
+       hlist_add_head(&obj->hash, head);
+
+       return obj;
+}
+
+static void cfi_hash_add(struct cfi_state *cfi)
+{
+       struct hlist_head *head = &cfi_hash[hash_min(cfi_key(cfi), cfi_bits)];
+
+       hlist_add_head(&cfi->hash, head);
+}
+
+static void *cfi_hash_alloc(unsigned long size)
+{
+       cfi_bits = max(10, ilog2(size));
+       cfi_hash = mmap(NULL, sizeof(struct hlist_head) << cfi_bits,
+                       PROT_READ|PROT_WRITE,
+                       MAP_PRIVATE|MAP_ANON, -1, 0);
+       if (cfi_hash == (void *)-1L) {
+               WARN("mmap fail cfi_hash");
+               cfi_hash = NULL;
+       }  else if (stats) {
+               printf("cfi_bits: %d\n", cfi_bits);
+       }
+
+       return cfi_hash;
+}
+
+static unsigned long nr_insns;
+static unsigned long nr_insns_visited;
+
 /*
  * Call the arch-specific instruction decoder for all the instructions and add
  * them to the global instruction list.
@@ -275,7 +353,6 @@ static int decode_instructions(struct objtool_file *file)
        struct symbol *func;
        unsigned long offset;
        struct instruction *insn;
-       unsigned long nr_insns = 0;
        int ret;
 
        for_each_sec(file, sec) {
@@ -301,12 +378,11 @@ static int decode_instructions(struct objtool_file *file)
                        memset(insn, 0, sizeof(*insn));
                        INIT_LIST_HEAD(&insn->alts);
                        INIT_LIST_HEAD(&insn->stack_ops);
-                       init_cfi_state(&insn->cfi);
 
                        insn->sec = sec;
                        insn->offset = offset;
 
-                       ret = arch_decode_instruction(file->elf, sec, offset,
+                       ret = arch_decode_instruction(file, sec, offset,
                                                      sec->sh.sh_size - offset,
                                                      &insn->len, &insn->type,
                                                      &insn->immediate,
@@ -344,6 +420,82 @@ err:
        return ret;
 }
 
+/*
+ * Read the pv_ops[] .data table to find the static initialized values.
+ */
+static int add_pv_ops(struct objtool_file *file, const char *symname)
+{
+       struct symbol *sym, *func;
+       unsigned long off, end;
+       struct reloc *rel;
+       int idx;
+
+       sym = find_symbol_by_name(file->elf, symname);
+       if (!sym)
+               return 0;
+
+       off = sym->offset;
+       end = off + sym->len;
+       for (;;) {
+               rel = find_reloc_by_dest_range(file->elf, sym->sec, off, end - off);
+               if (!rel)
+                       break;
+
+               func = rel->sym;
+               if (func->type == STT_SECTION)
+                       func = find_symbol_by_offset(rel->sym->sec, rel->addend);
+
+               idx = (rel->offset - sym->offset) / sizeof(unsigned long);
+
+               objtool_pv_add(file, idx, func);
+
+               off = rel->offset + 1;
+               if (off > end)
+                       break;
+       }
+
+       return 0;
+}
+
+/*
+ * Allocate and initialize file->pv_ops[].
+ */
+static int init_pv_ops(struct objtool_file *file)
+{
+       static const char *pv_ops_tables[] = {
+               "pv_ops",
+               "xen_cpu_ops",
+               "xen_irq_ops",
+               "xen_mmu_ops",
+               NULL,
+       };
+       const char *pv_ops;
+       struct symbol *sym;
+       int idx, nr;
+
+       if (!noinstr)
+               return 0;
+
+       file->pv_ops = NULL;
+
+       sym = find_symbol_by_name(file->elf, "pv_ops");
+       if (!sym)
+               return 0;
+
+       nr = sym->len / sizeof(unsigned long);
+       file->pv_ops = calloc(sizeof(struct pv_state), nr);
+       if (!file->pv_ops)
+               return -1;
+
+       for (idx = 0; idx < nr; idx++)
+               INIT_LIST_HEAD(&file->pv_ops[idx].targets);
+
+       for (idx = 0; (pv_ops = pv_ops_tables[idx]); idx++)
+               add_pv_ops(file, pv_ops);
+
+       return 0;
+}
+
 static struct instruction *find_last_insn(struct objtool_file *file,
                                          struct section *sec)
 {
@@ -531,6 +683,52 @@ static int create_static_call_sections(struct objtool_file *file)
        return 0;
 }
 
+static int create_retpoline_sites_sections(struct objtool_file *file)
+{
+       struct instruction *insn;
+       struct section *sec;
+       int idx;
+
+       sec = find_section_by_name(file->elf, ".retpoline_sites");
+       if (sec) {
+               WARN("file already has .retpoline_sites, skipping");
+               return 0;
+       }
+
+       idx = 0;
+       list_for_each_entry(insn, &file->retpoline_call_list, call_node)
+               idx++;
+
+       if (!idx)
+               return 0;
+
+       sec = elf_create_section(file->elf, ".retpoline_sites", 0,
+                                sizeof(int), idx);
+       if (!sec) {
+               WARN("elf_create_section: .retpoline_sites");
+               return -1;
+       }
+
+       idx = 0;
+       list_for_each_entry(insn, &file->retpoline_call_list, call_node) {
+
+               int *site = (int *)sec->data->d_buf + idx;
+               *site = 0;
+
+               if (elf_add_reloc_to_insn(file->elf, sec,
+                                         idx * sizeof(int),
+                                         R_X86_64_PC32,
+                                         insn->sec, insn->offset)) {
+                       WARN("elf_add_reloc_to_insn: .retpoline_sites");
+                       return -1;
+               }
+
+               idx++;
+       }
+
+       return 0;
+}
+
 static int create_mcount_loc_sections(struct objtool_file *file)
 {
        struct section *sec;
@@ -549,7 +747,7 @@ static int create_mcount_loc_sections(struct objtool_file *file)
                return 0;
 
        idx = 0;
-       list_for_each_entry(insn, &file->mcount_loc_list, mcount_loc_node)
+       list_for_each_entry(insn, &file->mcount_loc_list, call_node)
                idx++;
 
        sec = elf_create_section(file->elf, "__mcount_loc", 0, sizeof(unsigned long), idx);
@@ -557,7 +755,7 @@ static int create_mcount_loc_sections(struct objtool_file *file)
                return -1;
 
        idx = 0;
-       list_for_each_entry(insn, &file->mcount_loc_list, mcount_loc_node) {
+       list_for_each_entry(insn, &file->mcount_loc_list, call_node) {
 
                loc = (unsigned long *)sec->data->d_buf + idx;
                memset(loc, 0, sizeof(unsigned long));
@@ -651,6 +849,10 @@ static const char *uaccess_safe_builtin[] = {
        "__asan_report_store16_noabort",
        /* KCSAN */
        "__kcsan_check_access",
+       "__kcsan_mb",
+       "__kcsan_wmb",
+       "__kcsan_rmb",
+       "__kcsan_release",
        "kcsan_found_watchpoint",
        "kcsan_setup_watchpoint",
        "kcsan_check_scoped_accesses",
@@ -817,6 +1019,9 @@ static struct reloc *insn_reloc(struct objtool_file *file, struct instruction *i
                return NULL;
 
        if (!insn->reloc) {
+               if (!file)
+                       return NULL;
+
                insn->reloc = find_reloc_by_dest_range(file->elf, insn->sec,
                                                       insn->offset, insn->len);
                if (!insn->reloc) {
@@ -828,6 +1033,136 @@ static struct reloc *insn_reloc(struct objtool_file *file, struct instruction *i
        return insn->reloc;
 }
 
+static void remove_insn_ops(struct instruction *insn)
+{
+       struct stack_op *op, *tmp;
+
+       list_for_each_entry_safe(op, tmp, &insn->stack_ops, list) {
+               list_del(&op->list);
+               free(op);
+       }
+}
+
+static void annotate_call_site(struct objtool_file *file,
+                              struct instruction *insn, bool sibling)
+{
+       struct reloc *reloc = insn_reloc(file, insn);
+       struct symbol *sym = insn->call_dest;
+
+       if (!sym)
+               sym = reloc->sym;
+
+       /*
+        * Alternative replacement code is just template code which is
+        * sometimes copied to the original instruction. For now, don't
+        * annotate it. (In the future we might consider annotating the
+        * original instruction if/when it ever makes sense to do so.)
+        */
+       if (!strcmp(insn->sec->name, ".altinstr_replacement"))
+               return;
+
+       if (sym->static_call_tramp) {
+               list_add_tail(&insn->call_node, &file->static_call_list);
+               return;
+       }
+
+       if (sym->retpoline_thunk) {
+               list_add_tail(&insn->call_node, &file->retpoline_call_list);
+               return;
+       }
+
+       /*
+        * Many compilers cannot disable KCOV or sanitizer calls with a function
+        * attribute so they need a little help, NOP out any such calls from
+        * noinstr text.
+        */
+       if (insn->sec->noinstr && sym->profiling_func) {
+               if (reloc) {
+                       reloc->type = R_NONE;
+                       elf_write_reloc(file->elf, reloc);
+               }
+
+               elf_write_insn(file->elf, insn->sec,
+                              insn->offset, insn->len,
+                              sibling ? arch_ret_insn(insn->len)
+                                      : arch_nop_insn(insn->len));
+
+               insn->type = sibling ? INSN_RETURN : INSN_NOP;
+               return;
+       }
+
+       if (mcount && sym->fentry) {
+               if (sibling)
+                       WARN_FUNC("Tail call to __fentry__ !?!?", insn->sec, insn->offset);
+
+               if (reloc) {
+                       reloc->type = R_NONE;
+                       elf_write_reloc(file->elf, reloc);
+               }
+
+               elf_write_insn(file->elf, insn->sec,
+                              insn->offset, insn->len,
+                              arch_nop_insn(insn->len));
+
+               insn->type = INSN_NOP;
+
+               list_add_tail(&insn->call_node, &file->mcount_loc_list);
+               return;
+       }
+}
+
+static void add_call_dest(struct objtool_file *file, struct instruction *insn,
+                         struct symbol *dest, bool sibling)
+{
+       insn->call_dest = dest;
+       if (!dest)
+               return;
+
+       /*
+        * Whatever stack impact regular CALLs have, should be undone
+        * by the RETURN of the called function.
+        *
+        * Annotated intra-function calls retain the stack_ops but
+        * are converted to JUMP, see read_intra_function_calls().
+        */
+       remove_insn_ops(insn);
+
+       annotate_call_site(file, insn, sibling);
+}
+
+static void add_retpoline_call(struct objtool_file *file, struct instruction *insn)
+{
+       /*
+        * Retpoline calls/jumps are really dynamic calls/jumps in disguise,
+        * so convert them accordingly.
+        */
+       switch (insn->type) {
+       case INSN_CALL:
+               insn->type = INSN_CALL_DYNAMIC;
+               break;
+       case INSN_JUMP_UNCONDITIONAL:
+               insn->type = INSN_JUMP_DYNAMIC;
+               break;
+       case INSN_JUMP_CONDITIONAL:
+               insn->type = INSN_JUMP_DYNAMIC_CONDITIONAL;
+               break;
+       default:
+               return;
+       }
+
+       insn->retpoline_safe = true;
+
+       /*
+        * Whatever stack impact regular CALLs have, should be undone
+        * by the RETURN of the called function.
+        *
+        * Annotated intra-function calls retain the stack_ops but
+        * are converted to JUMP, see read_intra_function_calls().
+        */
+       remove_insn_ops(insn);
+
+       annotate_call_site(file, insn, false);
+}
 /*
  * Find the destination instructions for all jumps.
  */
@@ -849,28 +1184,12 @@ static int add_jump_destinations(struct objtool_file *file)
                } else if (reloc->sym->type == STT_SECTION) {
                        dest_sec = reloc->sym->sec;
                        dest_off = arch_dest_reloc_offset(reloc->addend);
-               } else if (arch_is_retpoline(reloc->sym)) {
-                       /*
-                        * Retpoline jumps are really dynamic jumps in
-                        * disguise, so convert them accordingly.
-                        */
-                       if (insn->type == INSN_JUMP_UNCONDITIONAL)
-                               insn->type = INSN_JUMP_DYNAMIC;
-                       else
-                               insn->type = INSN_JUMP_DYNAMIC_CONDITIONAL;
-
-                       list_add_tail(&insn->call_node,
-                                     &file->retpoline_call_list);
-
-                       insn->retpoline_safe = true;
+               } else if (reloc->sym->retpoline_thunk) {
+                       add_retpoline_call(file, insn);
                        continue;
                } else if (insn->func) {
                        /* internal or external sibling call (with reloc) */
-                       insn->call_dest = reloc->sym;
-                       if (insn->call_dest->static_call_tramp) {
-                               list_add_tail(&insn->call_node,
-                                             &file->static_call_list);
-                       }
+                       add_call_dest(file, insn, reloc->sym, true);
                        continue;
                } else if (reloc->sym->sec->idx) {
                        dest_sec = reloc->sym->sec;
@@ -926,13 +1245,8 @@ static int add_jump_destinations(struct objtool_file *file)
 
                        } else if (insn->jump_dest->func->pfunc != insn->func->pfunc &&
                                   insn->jump_dest->offset == insn->jump_dest->func->offset) {
-
                                /* internal sibling call (without reloc) */
-                               insn->call_dest = insn->jump_dest->func;
-                               if (insn->call_dest->static_call_tramp) {
-                                       list_add_tail(&insn->call_node,
-                                                     &file->static_call_list);
-                               }
+                               add_call_dest(file, insn, insn->jump_dest->func, true);
                        }
                }
        }
@@ -940,16 +1254,6 @@ static int add_jump_destinations(struct objtool_file *file)
        return 0;
 }
 
-static void remove_insn_ops(struct instruction *insn)
-{
-       struct stack_op *op, *tmp;
-
-       list_for_each_entry_safe(op, tmp, &insn->stack_ops, list) {
-               list_del(&op->list);
-               free(op);
-       }
-}
-
 static struct symbol *find_call_destination(struct section *sec, unsigned long offset)
 {
        struct symbol *call_dest;
@@ -968,6 +1272,7 @@ static int add_call_destinations(struct objtool_file *file)
 {
        struct instruction *insn;
        unsigned long dest_off;
+       struct symbol *dest;
        struct reloc *reloc;
 
        for_each_insn(file, insn) {
@@ -977,7 +1282,9 @@ static int add_call_destinations(struct objtool_file *file)
                reloc = insn_reloc(file, insn);
                if (!reloc) {
                        dest_off = arch_jump_destination(insn);
-                       insn->call_dest = find_call_destination(insn->sec, dest_off);
+                       dest = find_call_destination(insn->sec, dest_off);
+
+                       add_call_dest(file, insn, dest, false);
 
                        if (insn->ignore)
                                continue;
@@ -995,9 +1302,8 @@ static int add_call_destinations(struct objtool_file *file)
 
                } else if (reloc->sym->type == STT_SECTION) {
                        dest_off = arch_dest_reloc_offset(reloc->addend);
-                       insn->call_dest = find_call_destination(reloc->sym->sec,
-                                                               dest_off);
-                       if (!insn->call_dest) {
+                       dest = find_call_destination(reloc->sym->sec, dest_off);
+                       if (!dest) {
                                WARN_FUNC("can't find call dest symbol at %s+0x%lx",
                                          insn->sec, insn->offset,
                                          reloc->sym->sec->name,
@@ -1005,70 +1311,13 @@ static int add_call_destinations(struct objtool_file *file)
                                return -1;
                        }
 
-               } else if (arch_is_retpoline(reloc->sym)) {
-                       /*
-                        * Retpoline calls are really dynamic calls in
-                        * disguise, so convert them accordingly.
-                        */
-                       insn->type = INSN_CALL_DYNAMIC;
-                       insn->retpoline_safe = true;
+                       add_call_dest(file, insn, dest, false);
 
-                       list_add_tail(&insn->call_node,
-                                     &file->retpoline_call_list);
-
-                       remove_insn_ops(insn);
-                       continue;
+               } else if (reloc->sym->retpoline_thunk) {
+                       add_retpoline_call(file, insn);
 
                } else
-                       insn->call_dest = reloc->sym;
-
-               if (insn->call_dest && insn->call_dest->static_call_tramp) {
-                       list_add_tail(&insn->call_node,
-                                     &file->static_call_list);
-               }
-
-               /*
-                * Many compilers cannot disable KCOV with a function attribute
-                * so they need a little help, NOP out any KCOV calls from noinstr
-                * text.
-                */
-               if (insn->sec->noinstr &&
-                   !strncmp(insn->call_dest->name, "__sanitizer_cov_", 16)) {
-                       if (reloc) {
-                               reloc->type = R_NONE;
-                               elf_write_reloc(file->elf, reloc);
-                       }
-
-                       elf_write_insn(file->elf, insn->sec,
-                                      insn->offset, insn->len,
-                                      arch_nop_insn(insn->len));
-                       insn->type = INSN_NOP;
-               }
-
-               if (mcount && !strcmp(insn->call_dest->name, "__fentry__")) {
-                       if (reloc) {
-                               reloc->type = R_NONE;
-                               elf_write_reloc(file->elf, reloc);
-                       }
-
-                       elf_write_insn(file->elf, insn->sec,
-                                      insn->offset, insn->len,
-                                      arch_nop_insn(insn->len));
-
-                       insn->type = INSN_NOP;
-
-                       list_add_tail(&insn->mcount_loc_node,
-                                     &file->mcount_loc_list);
-               }
-
-               /*
-                * Whatever stack impact regular CALLs have, should be undone
-                * by the RETURN of the called function.
-                *
-                * Annotated intra-function calls retain the stack_ops but
-                * are converted to JUMP, see read_intra_function_calls().
-                */
-               remove_insn_ops(insn);
+                       add_call_dest(file, insn, reloc->sym, false);
        }
 
        return 0;
@@ -1136,7 +1385,6 @@ static int handle_group_alt(struct objtool_file *file,
                memset(nop, 0, sizeof(*nop));
                INIT_LIST_HEAD(&nop->alts);
                INIT_LIST_HEAD(&nop->stack_ops);
-               init_cfi_state(&nop->cfi);
 
                nop->sec = special_alt->new_sec;
                nop->offset = special_alt->new_off + special_alt->new_len;
@@ -1545,10 +1793,11 @@ static void set_func_state(struct cfi_state *state)
 
 static int read_unwind_hints(struct objtool_file *file)
 {
+       struct cfi_state cfi = init_cfi;
        struct section *sec, *relocsec;
-       struct reloc *reloc;
        struct unwind_hint *hint;
        struct instruction *insn;
+       struct reloc *reloc;
        int i;
 
        sec = find_section_by_name(file->elf, ".discard.unwind_hints");
@@ -1586,19 +1835,24 @@ static int read_unwind_hints(struct objtool_file *file)
                insn->hint = true;
 
                if (hint->type == UNWIND_HINT_TYPE_FUNC) {
-                       set_func_state(&insn->cfi);
+                       insn->cfi = &func_cfi;
                        continue;
                }
 
-               if (arch_decode_hint_reg(insn, hint->sp_reg)) {
+               if (insn->cfi)
+                       cfi = *(insn->cfi);
+
+               if (arch_decode_hint_reg(hint->sp_reg, &cfi.cfa.base)) {
                        WARN_FUNC("unsupported unwind_hint sp base reg %d",
                                  insn->sec, insn->offset, hint->sp_reg);
                        return -1;
                }
 
-               insn->cfi.cfa.offset = bswap_if_needed(hint->sp_offset);
-               insn->cfi.type = hint->type;
-               insn->cfi.end = hint->end;
+               cfi.cfa.offset = bswap_if_needed(hint->sp_offset);
+               cfi.type = hint->type;
+               cfi.end = hint->end;
+
+               insn->cfi = cfi_hash_find_or_add(&cfi);
        }
 
        return 0;
@@ -1737,17 +1991,53 @@ static int read_intra_function_calls(struct objtool_file *file)
        return 0;
 }
 
-static int read_static_call_tramps(struct objtool_file *file)
+/*
+ * Return true if name matches an instrumentation function, where calls to that
+ * function from noinstr code can safely be removed, but compilers won't do so.
+ */
+static bool is_profiling_func(const char *name)
+{
+       /*
+        * Many compilers cannot disable KCOV with a function attribute.
+        */
+       if (!strncmp(name, "__sanitizer_cov_", 16))
+               return true;
+
+       /*
+        * Some compilers currently do not remove __tsan_func_entry/exit nor
+        * __tsan_atomic_signal_fence (used for barrier instrumentation) with
+        * the __no_sanitize_thread attribute, remove them. Once the kernel's
+        * minimum Clang version is 14.0, this can be removed.
+        */
+       if (!strncmp(name, "__tsan_func_", 12) ||
+           !strcmp(name, "__tsan_atomic_signal_fence"))
+               return true;
+
+       return false;
+}
+
+static int classify_symbols(struct objtool_file *file)
 {
        struct section *sec;
        struct symbol *func;
 
        for_each_sec(file, sec) {
                list_for_each_entry(func, &sec->symbol_list, list) {
-                       if (func->bind == STB_GLOBAL &&
-                           !strncmp(func->name, STATIC_CALL_TRAMP_PREFIX_STR,
+                       if (func->bind != STB_GLOBAL)
+                               continue;
+
+                       if (!strncmp(func->name, STATIC_CALL_TRAMP_PREFIX_STR,
                                     strlen(STATIC_CALL_TRAMP_PREFIX_STR)))
                                func->static_call_tramp = true;
+
+                       if (arch_is_retpoline(func))
+                               func->retpoline_thunk = true;
+
+                       if (!strcmp(func->name, "__fentry__"))
+                               func->fentry = true;
+
+                       if (is_profiling_func(func->name))
+                               func->profiling_func = true;
                }
        }
 
@@ -1780,17 +2070,16 @@ static void mark_rodata(struct objtool_file *file)
        file->rodata = found;
 }
 
-__weak int arch_rewrite_retpolines(struct objtool_file *file)
-{
-       return 0;
-}
-
 static int decode_sections(struct objtool_file *file)
 {
        int ret;
 
        mark_rodata(file);
 
+       ret = init_pv_ops(file);
+       if (ret)
+               return ret;
+
        ret = decode_instructions(file);
        if (ret)
                return ret;
@@ -1809,7 +2098,7 @@ static int decode_sections(struct objtool_file *file)
        /*
         * Must be before add_{jump_call}_destination.
         */
-       ret = read_static_call_tramps(file);
+       ret = classify_symbols(file);
        if (ret)
                return ret;
 
@@ -1853,23 +2142,14 @@ static int decode_sections(struct objtool_file *file)
        if (ret)
                return ret;
 
-       /*
-        * Must be after add_special_section_alts(), since this will emit
-        * alternatives. Must be after add_{jump,call}_destination(), since
-        * those create the call insn lists.
-        */
-       ret = arch_rewrite_retpolines(file);
-       if (ret)
-               return ret;
-
        return 0;
 }
 
 static bool is_fentry_call(struct instruction *insn)
 {
-       if (insn->type == INSN_CALL && insn->call_dest &&
-           insn->call_dest->type == STT_NOTYPE &&
-           !strcmp(insn->call_dest->name, "__fentry__"))
+       if (insn->type == INSN_CALL &&
+           insn->call_dest &&
+           insn->call_dest->fentry)
                return true;
 
        return false;
@@ -2452,13 +2732,18 @@ static int propagate_alt_cfi(struct objtool_file *file, struct instruction *insn
        if (!insn->alt_group)
                return 0;
 
+       if (!insn->cfi) {
+               WARN("CFI missing");
+               return -1;
+       }
+
        alt_cfi = insn->alt_group->cfi;
        group_off = insn->offset - insn->alt_group->first_insn->offset;
 
        if (!alt_cfi[group_off]) {
-               alt_cfi[group_off] = &insn->cfi;
+               alt_cfi[group_off] = insn->cfi;
        } else {
-               if (memcmp(alt_cfi[group_off], &insn->cfi, sizeof(struct cfi_state))) {
+               if (cficmp(alt_cfi[group_off], insn->cfi)) {
                        WARN_FUNC("stack layout conflict in alternatives",
                                  insn->sec, insn->offset);
                        return -1;
@@ -2509,9 +2794,14 @@ static int handle_insn_ops(struct instruction *insn,
 
 static bool insn_cfi_match(struct instruction *insn, struct cfi_state *cfi2)
 {
-       struct cfi_state *cfi1 = &insn->cfi;
+       struct cfi_state *cfi1 = insn->cfi;
        int i;
 
+       if (!cfi1) {
+               WARN("CFI missing");
+               return false;
+       }
+
        if (memcmp(&cfi1->cfa, &cfi2->cfa, sizeof(cfi1->cfa))) {
 
                WARN_FUNC("stack state mismatch: cfa1=%d%+d cfa2=%d%+d",
@@ -2562,20 +2852,64 @@ static inline bool func_uaccess_safe(struct symbol *func)
 
 static inline const char *call_dest_name(struct instruction *insn)
 {
+       static char pvname[16];
+       struct reloc *rel;
+       int idx;
+
        if (insn->call_dest)
                return insn->call_dest->name;
 
+       rel = insn_reloc(NULL, insn);
+       if (rel && !strcmp(rel->sym->name, "pv_ops")) {
+               idx = (rel->addend / sizeof(void *));
+               snprintf(pvname, sizeof(pvname), "pv_ops[%d]", idx);
+               return pvname;
+       }
+
        return "{dynamic}";
 }
 
-static inline bool noinstr_call_dest(struct symbol *func)
+static bool pv_call_dest(struct objtool_file *file, struct instruction *insn)
+{
+       struct symbol *target;
+       struct reloc *rel;
+       int idx;
+
+       rel = insn_reloc(file, insn);
+       if (!rel || strcmp(rel->sym->name, "pv_ops"))
+               return false;
+
+       idx = (arch_dest_reloc_offset(rel->addend) / sizeof(void *));
+
+       if (file->pv_ops[idx].clean)
+               return true;
+
+       file->pv_ops[idx].clean = true;
+
+       list_for_each_entry(target, &file->pv_ops[idx].targets, pv_target) {
+               if (!target->sec->noinstr) {
+                       WARN("pv_ops[%d]: %s", idx, target->name);
+                       file->pv_ops[idx].clean = false;
+               }
+       }
+
+       return file->pv_ops[idx].clean;
+}
+
+static inline bool noinstr_call_dest(struct objtool_file *file,
+                                    struct instruction *insn,
+                                    struct symbol *func)
 {
        /*
         * We can't deal with indirect function calls at present;
         * assume they're instrumented.
         */
-       if (!func)
+       if (!func) {
+               if (file->pv_ops)
+                       return pv_call_dest(file, insn);
+
                return false;
+       }
 
        /*
         * If the symbol is from a noinstr section; we good.
@@ -2594,10 +2928,12 @@ static inline bool noinstr_call_dest(struct symbol *func)
        return false;
 }
 
-static int validate_call(struct instruction *insn, struct insn_state *state)
+static int validate_call(struct objtool_file *file,
+                        struct instruction *insn,
+                        struct insn_state *state)
 {
        if (state->noinstr && state->instr <= 0 &&
-           !noinstr_call_dest(insn->call_dest)) {
+           !noinstr_call_dest(file, insn, insn->call_dest)) {
                WARN_FUNC("call to %s() leaves .noinstr.text section",
                                insn->sec, insn->offset, call_dest_name(insn));
                return 1;
@@ -2618,7 +2954,9 @@ static int validate_call(struct instruction *insn, struct insn_state *state)
        return 0;
 }
 
-static int validate_sibling_call(struct instruction *insn, struct insn_state *state)
+static int validate_sibling_call(struct objtool_file *file,
+                                struct instruction *insn,
+                                struct insn_state *state)
 {
        if (has_modified_stack_frame(insn, state)) {
                WARN_FUNC("sibling call from callable instruction with modified stack frame",
@@ -2626,7 +2964,7 @@ static int validate_sibling_call(struct instruction *insn, struct insn_state *st
                return 1;
        }
 
-       return validate_call(insn, state);
+       return validate_call(file, insn, state);
 }
 
 static int validate_return(struct symbol *func, struct instruction *insn, struct insn_state *state)
@@ -2696,7 +3034,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
                           struct instruction *insn, struct insn_state state)
 {
        struct alternative *alt;
-       struct instruction *next_insn;
+       struct instruction *next_insn, *prev_insn = NULL;
        struct section *sec;
        u8 visited;
        int ret;
@@ -2725,15 +3063,25 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
 
                        if (insn->visited & visited)
                                return 0;
+               } else {
+                       nr_insns_visited++;
                }
 
                if (state.noinstr)
                        state.instr += insn->instr;
 
-               if (insn->hint)
-                       state.cfi = insn->cfi;
-               else
-                       insn->cfi = state.cfi;
+               if (insn->hint) {
+                       state.cfi = *insn->cfi;
+               } else {
+                       /* XXX track if we actually changed state.cfi */
+
+                       if (prev_insn && !cficmp(prev_insn->cfi, &state.cfi)) {
+                               insn->cfi = prev_insn->cfi;
+                               nr_cfi_reused++;
+                       } else {
+                               insn->cfi = cfi_hash_find_or_add(&state.cfi);
+                       }
+               }
 
                insn->visited |= visited;
 
@@ -2769,7 +3117,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
 
                case INSN_CALL:
                case INSN_CALL_DYNAMIC:
-                       ret = validate_call(insn, &state);
+                       ret = validate_call(file, insn, &state);
                        if (ret)
                                return ret;
 
@@ -2788,7 +3136,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
                case INSN_JUMP_CONDITIONAL:
                case INSN_JUMP_UNCONDITIONAL:
                        if (is_sibling_call(insn)) {
-                               ret = validate_sibling_call(insn, &state);
+                               ret = validate_sibling_call(file, insn, &state);
                                if (ret)
                                        return ret;
 
@@ -2810,7 +3158,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
                case INSN_JUMP_DYNAMIC:
                case INSN_JUMP_DYNAMIC_CONDITIONAL:
                        if (is_sibling_call(insn)) {
-                               ret = validate_sibling_call(insn, &state);
+                               ret = validate_sibling_call(file, insn, &state);
                                if (ret)
                                        return ret;
                        }
@@ -2883,6 +3231,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
                        return 1;
                }
 
+               prev_insn = insn;
                insn = next_insn;
        }
 
@@ -2909,7 +3258,7 @@ static int validate_unwind_hints(struct objtool_file *file, struct section *sec)
        }
 
        while (&insn->list != &file->insn_list && (!sec || insn->sec == sec)) {
-               if (insn->hint && !insn->visited) {
+               if (insn->hint && !insn->visited && !insn->ignore) {
                        ret = validate_branch(file, insn->func, insn, state);
                        if (ret && backtrace)
                                BT_FUNC("<=== (hint)", insn);
@@ -2990,6 +3339,9 @@ static bool ignore_unreachable_insn(struct objtool_file *file, struct instructio
        if (!insn->func)
                return false;
 
+       if (insn->func->static_call_tramp)
+               return true;
+
        /*
         * CONFIG_UBSAN_TRAP inserts a UD2 when it sees
         * __builtin_unreachable().  The BUG() macro has an unreachable() after
@@ -3138,10 +3490,20 @@ int check(struct objtool_file *file)
        int ret, warnings = 0;
 
        arch_initial_func_cfi_state(&initial_func_cfi);
+       init_cfi_state(&init_cfi);
+       init_cfi_state(&func_cfi);
+       set_func_state(&func_cfi);
+
+       if (!cfi_hash_alloc(1UL << (file->elf->symbol_bits - 3)))
+               goto out;
+
+       cfi_hash_add(&init_cfi);
+       cfi_hash_add(&func_cfi);
 
        ret = decode_sections(file);
        if (ret < 0)
                goto out;
+
        warnings += ret;
 
        if (list_empty(&file->insn_list))
@@ -3185,6 +3547,13 @@ int check(struct objtool_file *file)
                goto out;
        warnings += ret;
 
+       if (retpoline) {
+               ret = create_retpoline_sites_sections(file);
+               if (ret < 0)
+                       goto out;
+               warnings += ret;
+       }
+
        if (mcount) {
                ret = create_mcount_loc_sections(file);
                if (ret < 0)
@@ -3192,6 +3561,13 @@ int check(struct objtool_file *file)
                warnings += ret;
        }
 
+       if (stats) {
+               printf("nr_insns_visited: %ld\n", nr_insns_visited);
+               printf("nr_cfi: %ld\n", nr_cfi);
+               printf("nr_cfi_reused: %ld\n", nr_cfi_reused);
+               printf("nr_cfi_cache: %ld\n", nr_cfi_cache);
+       }
+
 out:
        /*
         *  For now, don't fail the kernel build on fatal warnings.  These