]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/commitdiff
add 4.9 patches but no announcement as I am probably going to just drop them
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 30 May 2018 19:56:05 +0000 (21:56 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 30 May 2018 19:56:05 +0000 (21:56 +0200)
queue-4.9/objtool-improve-detection-of-bug-and-other-dead-ends.patch [new file with mode: 0644]
queue-4.9/objtool-move-checking-code-to-check.c.patch [new file with mode: 0644]
queue-4.9/series

diff --git a/queue-4.9/objtool-improve-detection-of-bug-and-other-dead-ends.patch b/queue-4.9/objtool-improve-detection-of-bug-and-other-dead-ends.patch
new file mode 100644 (file)
index 0000000..58a08c0
--- /dev/null
@@ -0,0 +1,207 @@
+From d1091c7fa3d52ebce4dd3f15d04155b3469b2f90 Mon Sep 17 00:00:00 2001
+From: Josh Poimboeuf <jpoimboe@redhat.com>
+Date: Tue, 21 Feb 2017 15:35:32 -0600
+Subject: objtool: Improve detection of BUG() and other dead ends
+
+From: Josh Poimboeuf <jpoimboe@redhat.com>
+
+commit d1091c7fa3d52ebce4dd3f15d04155b3469b2f90 upstream.
+
+The BUG() macro's use of __builtin_unreachable() via the unreachable()
+macro tells gcc that the instruction is a dead end, and that it's safe
+to assume the current code path will not execute past the previous
+instruction.
+
+On x86, the BUG() macro is implemented with the 'ud2' instruction.  When
+objtool's branch analysis sees that instruction, it knows the current
+code path has come to a dead end.
+
+Peter Zijlstra has been working on a patch to change the WARN macros to
+use 'ud2'.  That patch will break objtool's assumption that 'ud2' is
+always a dead end.
+
+Generally it's best for objtool to avoid making those kinds of
+assumptions anyway.  The more ignorant it is of kernel code internals,
+the better.
+
+So create a more generic way for objtool to detect dead ends by adding
+an annotation to the unreachable() macro.  The annotation stores a
+pointer to the end of the unreachable code path in an '__unreachable'
+section.  Objtool can read that section to find the dead ends.
+
+Tested-by: Peter Zijlstra (Intel) <peterz@infradead.org>
+Signed-off-by: Josh Poimboeuf <jpoimboe@redhat.com>
+Cc: Linus Torvalds <torvalds@linux-foundation.org>
+Cc: Peter Zijlstra <peterz@infradead.org>
+Cc: Thomas Gleixner <tglx@linutronix.de>
+Link: http://lkml.kernel.org/r/41a6d33971462ebd944a1c60ad4bf5be86c17b77.1487712920.git.jpoimboe@redhat.com
+Signed-off-by: Ingo Molnar <mingo@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+
+---
+ arch/x86/kernel/vmlinux.lds.S   |    1 
+ include/linux/compiler-gcc.h    |   13 ++++++++
+ tools/objtool/arch.h            |    5 +--
+ tools/objtool/arch/x86/decode.c |    3 --
+ tools/objtool/builtin-check.c   |   60 +++++++++++++++++++++++++++++++++++++---
+ 5 files changed, 71 insertions(+), 11 deletions(-)
+
+--- a/arch/x86/kernel/vmlinux.lds.S
++++ b/arch/x86/kernel/vmlinux.lds.S
+@@ -353,6 +353,7 @@ SECTIONS
+       /DISCARD/ : {
+               *(.eh_frame)
+               *(__func_stack_frame_non_standard)
++              *(__unreachable)
+       }
+ }
+--- a/include/linux/compiler-gcc.h
++++ b/include/linux/compiler-gcc.h
+@@ -203,6 +203,17 @@
+ #endif
+ #endif
++#ifdef CONFIG_STACK_VALIDATION
++#define annotate_unreachable() ({                                     \
++      asm("1:\t\n"                                                    \
++          ".pushsection __unreachable, \"a\"\t\n"                     \
++          ".long 1b\t\n"                                              \
++          ".popsection\t\n");                                         \
++})
++#else
++#define annotate_unreachable()
++#endif
++
+ /*
+  * Mark a position in code as unreachable.  This can be used to
+  * suppress control flow warnings after asm blocks that transfer
+@@ -212,7 +223,7 @@
+  * this in the preprocessor, but we can live with this because they're
+  * unreleased.  Really, we need to have autoconf for the kernel.
+  */
+-#define unreachable() __builtin_unreachable()
++#define unreachable() annotate_unreachable(); __builtin_unreachable()
+ /* Mark a function definition as prohibited from being cloned. */
+ #define __noclone     __attribute__((__noclone__, __optimize__("no-tracer")))
+--- a/tools/objtool/arch.h
++++ b/tools/objtool/arch.h
+@@ -31,9 +31,8 @@
+ #define INSN_CALL_DYNAMIC     8
+ #define INSN_RETURN           9
+ #define INSN_CONTEXT_SWITCH   10
+-#define INSN_BUG              11
+-#define INSN_NOP              12
+-#define INSN_OTHER            13
++#define INSN_NOP              11
++#define INSN_OTHER            12
+ #define INSN_LAST             INSN_OTHER
+ int arch_decode_instruction(struct elf *elf, struct section *sec,
+--- a/tools/objtool/arch/x86/decode.c
++++ b/tools/objtool/arch/x86/decode.c
+@@ -118,9 +118,6 @@ int arch_decode_instruction(struct elf *
+                        op2 == 0x35)
+                       /* sysenter, sysret */
+                       *type = INSN_CONTEXT_SWITCH;
+-              else if (op2 == 0x0b || op2 == 0xb9)
+-                      /* ud2 */
+-                      *type = INSN_BUG;
+               else if (op2 == 0x0d || op2 == 0x1f)
+                       /* nopl/nopw */
+                       *type = INSN_NOP;
+--- a/tools/objtool/builtin-check.c
++++ b/tools/objtool/builtin-check.c
+@@ -51,7 +51,7 @@ struct instruction {
+       unsigned int len, state;
+       unsigned char type;
+       unsigned long immediate;
+-      bool alt_group, visited, ignore_alts;
++      bool alt_group, visited, dead_end, ignore_alts;
+       struct symbol *call_dest;
+       struct instruction *jump_dest;
+       struct list_head alts;
+@@ -330,6 +330,54 @@ static int decode_instructions(struct ob
+ }
+ /*
++ * Find all uses of the unreachable() macro, which are code path dead ends.
++ */
++static int add_dead_ends(struct objtool_file *file)
++{
++      struct section *sec;
++      struct rela *rela;
++      struct instruction *insn;
++      bool found;
++
++      sec = find_section_by_name(file->elf, ".rela__unreachable");
++      if (!sec)
++              return 0;
++
++      list_for_each_entry(rela, &sec->rela_list, list) {
++              if (rela->sym->type != STT_SECTION) {
++                      WARN("unexpected relocation symbol type in .rela__unreachable");
++                      return -1;
++              }
++              insn = find_insn(file, rela->sym->sec, rela->addend);
++              if (insn)
++                      insn = list_prev_entry(insn, list);
++              else if (rela->addend == rela->sym->sec->len) {
++                      found = false;
++                      list_for_each_entry_reverse(insn, &file->insn_list, list) {
++                              if (insn->sec == rela->sym->sec) {
++                                      found = true;
++                                      break;
++                              }
++                      }
++
++                      if (!found) {
++                              WARN("can't find unreachable insn at %s+0x%x",
++                                   rela->sym->sec->name, rela->addend);
++                              return -1;
++                      }
++              } else {
++                      WARN("can't find unreachable insn at %s+0x%x",
++                           rela->sym->sec->name, rela->addend);
++                      return -1;
++              }
++
++              insn->dead_end = true;
++      }
++
++      return 0;
++}
++
++/*
+  * Warnings shouldn't be reported for ignored functions.
+  */
+ static void add_ignores(struct objtool_file *file)
+@@ -905,6 +953,10 @@ static int decode_sections(struct objtoo
+       if (ret)
+               return ret;
++      ret = add_dead_ends(file);
++      if (ret)
++              return ret;
++
+       add_ignores(file);
+       ret = add_nospec_ignores(file);
+@@ -1103,13 +1155,13 @@ static int validate_branch(struct objtoo
+                       return 0;
+-              case INSN_BUG:
+-                      return 0;
+-
+               default:
+                       break;
+               }
++              if (insn->dead_end)
++                      return 0;
++
+               insn = next_insn_same_sec(file, insn);
+               if (!insn) {
+                       WARN("%s: unexpected end of section", sec->name);
diff --git a/queue-4.9/objtool-move-checking-code-to-check.c.patch b/queue-4.9/objtool-move-checking-code-to-check.c.patch
new file mode 100644 (file)
index 0000000..3210da9
--- /dev/null
@@ -0,0 +1,2798 @@
+From dcc914f44f065ef73685b37e59877a5bb3cb7358 Mon Sep 17 00:00:00 2001
+From: Josh Poimboeuf <jpoimboe@redhat.com>
+Date: Wed, 28 Jun 2017 10:11:05 -0500
+Subject: objtool: Move checking code to check.c
+
+From: Josh Poimboeuf <jpoimboe@redhat.com>
+
+commit dcc914f44f065ef73685b37e59877a5bb3cb7358 upstream.
+
+In preparation for the new 'objtool undwarf generate' command, which
+will rely on 'objtool check', move the checking code from
+builtin-check.c to check.c where it can be used by other commands.
+
+Signed-off-by: Josh Poimboeuf <jpoimboe@redhat.com>
+Reviewed-by: Jiri Slaby <jslaby@suse.cz>
+Cc: Andy Lutomirski <luto@kernel.org>
+Cc: Linus Torvalds <torvalds@linux-foundation.org>
+Cc: Peter Zijlstra <peterz@infradead.org>
+Cc: Thomas Gleixner <tglx@linutronix.de>
+Cc: live-patching@vger.kernel.org
+Link: http://lkml.kernel.org/r/294c5c695fd73c1a5000bbe5960a7c9bec4ee6b4.1498659915.git.jpoimboe@redhat.com
+Signed-off-by: Ingo Molnar <mingo@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+
+---
+ tools/objtool/Build           |    1 
+ tools/objtool/builtin-check.c | 1346 ------------------------------------------
+ tools/objtool/check.c         | 1327 +++++++++++++++++++++++++++++++++++++++++
+ tools/objtool/check.h         |   51 +
+ 4 files changed, 1392 insertions(+), 1333 deletions(-)
+
+--- a/tools/objtool/Build
++++ b/tools/objtool/Build
+@@ -1,5 +1,6 @@
+ objtool-y += arch/$(SRCARCH)/
+ objtool-y += builtin-check.o
++objtool-y += check.o
+ objtool-y += elf.o
+ objtool-y += special.o
+ objtool-y += objtool.o
+--- a/tools/objtool/builtin-check.c
++++ b/tools/objtool/builtin-check.c
+@@ -1,5 +1,5 @@
+ /*
+- * Copyright (C) 2015 Josh Poimboeuf <jpoimboe@redhat.com>
++ * Copyright (C) 2015-2017 Josh Poimboeuf <jpoimboe@redhat.com>
+  *
+  * This program is free software; you can redistribute it and/or
+  * modify it under the terms of the GNU General Public License
+@@ -25,1352 +25,32 @@
+  * For more information, see tools/objtool/Documentation/stack-validation.txt.
+  */
+-#include <string.h>
+-#include <stdlib.h>
+ #include <subcmd/parse-options.h>
+-
+ #include "builtin.h"
+-#include "elf.h"
+-#include "special.h"
+-#include "arch.h"
+-#include "warn.h"
+-
+-#include <linux/hashtable.h>
+-
+-#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+-
+-#define STATE_FP_SAVED                0x1
+-#define STATE_FP_SETUP                0x2
+-#define STATE_FENTRY          0x4
+-
+-struct instruction {
+-      struct list_head list;
+-      struct hlist_node hash;
+-      struct section *sec;
+-      unsigned long offset;
+-      unsigned int len, state;
+-      unsigned char type;
+-      unsigned long immediate;
+-      bool alt_group, visited, dead_end, ignore_alts;
+-      struct symbol *call_dest;
+-      struct instruction *jump_dest;
+-      struct list_head alts;
+-      struct symbol *func;
+-};
+-
+-struct alternative {
+-      struct list_head list;
+-      struct instruction *insn;
+-};
+-
+-struct objtool_file {
+-      struct elf *elf;
+-      struct list_head insn_list;
+-      DECLARE_HASHTABLE(insn_hash, 16);
+-      struct section *rodata, *whitelist;
+-      bool ignore_unreachables, c_file;
+-};
+-
+-const char *objname;
+-static bool nofp;
+-
+-static struct instruction *find_insn(struct objtool_file *file,
+-                                   struct section *sec, unsigned long offset)
+-{
+-      struct instruction *insn;
+-
+-      hash_for_each_possible(file->insn_hash, insn, hash, offset)
+-              if (insn->sec == sec && insn->offset == offset)
+-                      return insn;
+-
+-      return NULL;
+-}
+-
+-static struct instruction *next_insn_same_sec(struct objtool_file *file,
+-                                            struct instruction *insn)
+-{
+-      struct instruction *next = list_next_entry(insn, list);
+-
+-      if (&next->list == &file->insn_list || next->sec != insn->sec)
+-              return NULL;
+-
+-      return next;
+-}
+-
+-static bool gcov_enabled(struct objtool_file *file)
+-{
+-      struct section *sec;
+-      struct symbol *sym;
+-
+-      list_for_each_entry(sec, &file->elf->sections, list)
+-              list_for_each_entry(sym, &sec->symbol_list, list)
+-                      if (!strncmp(sym->name, "__gcov_.", 8))
+-                              return true;
+-
+-      return false;
+-}
+-
+-#define for_each_insn(file, insn)                                     \
+-      list_for_each_entry(insn, &file->insn_list, list)
+-
+-#define func_for_each_insn(file, func, insn)                          \
+-      for (insn = find_insn(file, func->sec, func->offset);           \
+-           insn && &insn->list != &file->insn_list &&                 \
+-              insn->sec == func->sec &&                               \
+-              insn->offset < func->offset + func->len;                \
+-           insn = list_next_entry(insn, list))
+-
+-#define func_for_each_insn_continue_reverse(file, func, insn)         \
+-      for (insn = list_prev_entry(insn, list);                        \
+-           &insn->list != &file->insn_list &&                         \
+-              insn->sec == func->sec && insn->offset >= func->offset; \
+-           insn = list_prev_entry(insn, list))
+-
+-#define sec_for_each_insn_from(file, insn)                            \
+-      for (; insn; insn = next_insn_same_sec(file, insn))
+-
+-
+-/*
+- * Check if the function has been manually whitelisted with the
+- * STACK_FRAME_NON_STANDARD macro, or if it should be automatically whitelisted
+- * due to its use of a context switching instruction.
+- */
+-static bool ignore_func(struct objtool_file *file, struct symbol *func)
+-{
+-      struct rela *rela;
+-      struct instruction *insn;
+-
+-      /* check for STACK_FRAME_NON_STANDARD */
+-      if (file->whitelist && file->whitelist->rela)
+-              list_for_each_entry(rela, &file->whitelist->rela->rela_list, list) {
+-                      if (rela->sym->type == STT_SECTION &&
+-                          rela->sym->sec == func->sec &&
+-                          rela->addend == func->offset)
+-                              return true;
+-                      if (rela->sym->type == STT_FUNC && rela->sym == func)
+-                              return true;
+-              }
+-
+-      /* check if it has a context switching instruction */
+-      func_for_each_insn(file, func, insn)
+-              if (insn->type == INSN_CONTEXT_SWITCH)
+-                      return true;
+-
+-      return false;
+-}
+-
+-/*
+- * This checks to see if the given function is a "noreturn" function.
+- *
+- * For global functions which are outside the scope of this object file, we
+- * have to keep a manual list of them.
+- *
+- * For local functions, we have to detect them manually by simply looking for
+- * the lack of a return instruction.
+- *
+- * Returns:
+- *  -1: error
+- *   0: no dead end
+- *   1: dead end
+- */
+-static int __dead_end_function(struct objtool_file *file, struct symbol *func,
+-                             int recursion)
+-{
+-      int i;
+-      struct instruction *insn;
+-      bool empty = true;
+-
+-      /*
+-       * Unfortunately these have to be hard coded because the noreturn
+-       * attribute isn't provided in ELF data.
+-       */
+-      static const char * const global_noreturns[] = {
+-              "__stack_chk_fail",
+-              "panic",
+-              "do_exit",
+-              "do_task_dead",
+-              "__module_put_and_exit",
+-              "complete_and_exit",
+-              "kvm_spurious_fault",
+-              "__reiserfs_panic",
+-              "lbug_with_loc"
+-      };
+-
+-      if (func->bind == STB_WEAK)
+-              return 0;
+-
+-      if (func->bind == STB_GLOBAL)
+-              for (i = 0; i < ARRAY_SIZE(global_noreturns); i++)
+-                      if (!strcmp(func->name, global_noreturns[i]))
+-                              return 1;
+-
+-      if (!func->sec)
+-              return 0;
+-
+-      func_for_each_insn(file, func, insn) {
+-              empty = false;
+-
+-              if (insn->type == INSN_RETURN)
+-                      return 0;
+-      }
+-
+-      if (empty)
+-              return 0;
+-
+-      /*
+-       * A function can have a sibling call instead of a return.  In that
+-       * case, the function's dead-end status depends on whether the target
+-       * of the sibling call returns.
+-       */
+-      func_for_each_insn(file, func, insn) {
+-              if (insn->sec != func->sec ||
+-                  insn->offset >= func->offset + func->len)
+-                      break;
+-
+-              if (insn->type == INSN_JUMP_UNCONDITIONAL) {
+-                      struct instruction *dest = insn->jump_dest;
+-                      struct symbol *dest_func;
+-
+-                      if (!dest)
+-                              /* sibling call to another file */
+-                              return 0;
+-
+-                      if (dest->sec != func->sec ||
+-                          dest->offset < func->offset ||
+-                          dest->offset >= func->offset + func->len) {
+-                              /* local sibling call */
+-                              dest_func = find_symbol_by_offset(dest->sec,
+-                                                                dest->offset);
+-                              if (!dest_func)
+-                                      continue;
+-
+-                              if (recursion == 5) {
+-                                      WARN_FUNC("infinite recursion (objtool bug!)",
+-                                                dest->sec, dest->offset);
+-                                      return -1;
+-                              }
+-
+-                              return __dead_end_function(file, dest_func,
+-                                                         recursion + 1);
+-                      }
+-              }
+-
+-              if (insn->type == INSN_JUMP_DYNAMIC && list_empty(&insn->alts))
+-                      /* sibling call */
+-                      return 0;
+-      }
+-
+-      return 1;
+-}
+-
+-static int dead_end_function(struct objtool_file *file, struct symbol *func)
+-{
+-      return __dead_end_function(file, func, 0);
+-}
+-
+-/*
+- * Call the arch-specific instruction decoder for all the instructions and add
+- * them to the global instruction list.
+- */
+-static int decode_instructions(struct objtool_file *file)
+-{
+-      struct section *sec;
+-      struct symbol *func;
+-      unsigned long offset;
+-      struct instruction *insn;
+-      int ret;
+-
+-      list_for_each_entry(sec, &file->elf->sections, list) {
+-
+-              if (!(sec->sh.sh_flags & SHF_EXECINSTR))
+-                      continue;
+-
+-              for (offset = 0; offset < sec->len; offset += insn->len) {
+-                      insn = malloc(sizeof(*insn));
+-                      memset(insn, 0, sizeof(*insn));
+-
+-                      INIT_LIST_HEAD(&insn->alts);
+-                      insn->sec = sec;
+-                      insn->offset = offset;
+-
+-                      ret = arch_decode_instruction(file->elf, sec, offset,
+-                                                    sec->len - offset,
+-                                                    &insn->len, &insn->type,
+-                                                    &insn->immediate);
+-                      if (ret)
+-                              return ret;
+-
+-                      if (!insn->type || insn->type > INSN_LAST) {
+-                              WARN_FUNC("invalid instruction type %d",
+-                                        insn->sec, insn->offset, insn->type);
+-                              return -1;
+-                      }
+-
+-                      hash_add(file->insn_hash, &insn->hash, insn->offset);
+-                      list_add_tail(&insn->list, &file->insn_list);
+-              }
+-
+-              list_for_each_entry(func, &sec->symbol_list, list) {
+-                      if (func->type != STT_FUNC)
+-                              continue;
+-
+-                      if (!find_insn(file, sec, func->offset)) {
+-                              WARN("%s(): can't find starting instruction",
+-                                   func->name);
+-                              return -1;
+-                      }
+-
+-                      func_for_each_insn(file, func, insn)
+-                              if (!insn->func)
+-                                      insn->func = func;
+-              }
+-      }
+-
+-      return 0;
+-}
+-
+-/*
+- * Find all uses of the unreachable() macro, which are code path dead ends.
+- */
+-static int add_dead_ends(struct objtool_file *file)
+-{
+-      struct section *sec;
+-      struct rela *rela;
+-      struct instruction *insn;
+-      bool found;
+-
+-      sec = find_section_by_name(file->elf, ".rela__unreachable");
+-      if (!sec)
+-              return 0;
+-
+-      list_for_each_entry(rela, &sec->rela_list, list) {
+-              if (rela->sym->type != STT_SECTION) {
+-                      WARN("unexpected relocation symbol type in .rela__unreachable");
+-                      return -1;
+-              }
+-              insn = find_insn(file, rela->sym->sec, rela->addend);
+-              if (insn)
+-                      insn = list_prev_entry(insn, list);
+-              else if (rela->addend == rela->sym->sec->len) {
+-                      found = false;
+-                      list_for_each_entry_reverse(insn, &file->insn_list, list) {
+-                              if (insn->sec == rela->sym->sec) {
+-                                      found = true;
+-                                      break;
+-                              }
+-                      }
+-
+-                      if (!found) {
+-                              WARN("can't find unreachable insn at %s+0x%x",
+-                                   rela->sym->sec->name, rela->addend);
+-                              return -1;
+-                      }
+-              } else {
+-                      WARN("can't find unreachable insn at %s+0x%x",
+-                           rela->sym->sec->name, rela->addend);
+-                      return -1;
+-              }
+-
+-              insn->dead_end = true;
+-      }
+-
+-      return 0;
+-}
+-
+-/*
+- * Warnings shouldn't be reported for ignored functions.
+- */
+-static void add_ignores(struct objtool_file *file)
+-{
+-      struct instruction *insn;
+-      struct section *sec;
+-      struct symbol *func;
+-
+-      list_for_each_entry(sec, &file->elf->sections, list) {
+-              list_for_each_entry(func, &sec->symbol_list, list) {
+-                      if (func->type != STT_FUNC)
+-                              continue;
+-
+-                      if (!ignore_func(file, func))
+-                              continue;
+-
+-                      func_for_each_insn(file, func, insn)
+-                              insn->visited = true;
+-              }
+-      }
+-}
+-
+-/*
+- * FIXME: For now, just ignore any alternatives which add retpolines.  This is
+- * a temporary hack, as it doesn't allow ORC to unwind from inside a retpoline.
+- * But it at least allows objtool to understand the control flow *around* the
+- * retpoline.
+- */
+-static int add_nospec_ignores(struct objtool_file *file)
+-{
+-      struct section *sec;
+-      struct rela *rela;
+-      struct instruction *insn;
+-
+-      sec = find_section_by_name(file->elf, ".rela.discard.nospec");
+-      if (!sec)
+-              return 0;
+-
+-      list_for_each_entry(rela, &sec->rela_list, list) {
+-              if (rela->sym->type != STT_SECTION) {
+-                      WARN("unexpected relocation symbol type in %s", sec->name);
+-                      return -1;
+-              }
+-
+-              insn = find_insn(file, rela->sym->sec, rela->addend);
+-              if (!insn) {
+-                      WARN("bad .discard.nospec entry");
+-                      return -1;
+-              }
+-
+-              insn->ignore_alts = true;
+-      }
+-
+-      return 0;
+-}
+-
+-/*
+- * Find the destination instructions for all jumps.
+- */
+-static int add_jump_destinations(struct objtool_file *file)
+-{
+-      struct instruction *insn;
+-      struct rela *rela;
+-      struct section *dest_sec;
+-      unsigned long dest_off;
+-
+-      for_each_insn(file, insn) {
+-              if (insn->type != INSN_JUMP_CONDITIONAL &&
+-                  insn->type != INSN_JUMP_UNCONDITIONAL)
+-                      continue;
+-
+-              /* skip ignores */
+-              if (insn->visited)
+-                      continue;
+-
+-              rela = find_rela_by_dest_range(insn->sec, insn->offset,
+-                                             insn->len);
+-              if (!rela) {
+-                      dest_sec = insn->sec;
+-                      dest_off = insn->offset + insn->len + insn->immediate;
+-              } else if (rela->sym->type == STT_SECTION) {
+-                      dest_sec = rela->sym->sec;
+-                      dest_off = rela->addend + 4;
+-              } else if (rela->sym->sec->idx) {
+-                      dest_sec = rela->sym->sec;
+-                      dest_off = rela->sym->sym.st_value + rela->addend + 4;
+-              } else if (strstr(rela->sym->name, "_indirect_thunk_")) {
+-                      /*
+-                       * Retpoline jumps are really dynamic jumps in
+-                       * disguise, so convert them accordingly.
+-                       */
+-                      insn->type = INSN_JUMP_DYNAMIC;
+-                      continue;
+-              } else {
+-                      /* sibling call */
+-                      insn->jump_dest = 0;
+-                      continue;
+-              }
+-
+-              insn->jump_dest = find_insn(file, dest_sec, dest_off);
+-              if (!insn->jump_dest) {
+-
+-                      /*
+-                       * This is a special case where an alt instruction
+-                       * jumps past the end of the section.  These are
+-                       * handled later in handle_group_alt().
+-                       */
+-                      if (!strcmp(insn->sec->name, ".altinstr_replacement"))
+-                              continue;
+-
+-                      WARN_FUNC("can't find jump dest instruction at %s+0x%lx",
+-                                insn->sec, insn->offset, dest_sec->name,
+-                                dest_off);
+-                      return -1;
+-              }
+-      }
+-
+-      return 0;
+-}
+-
+-/*
+- * Find the destination instructions for all calls.
+- */
+-static int add_call_destinations(struct objtool_file *file)
+-{
+-      struct instruction *insn;
+-      unsigned long dest_off;
+-      struct rela *rela;
+-
+-      for_each_insn(file, insn) {
+-              if (insn->type != INSN_CALL)
+-                      continue;
+-
+-              rela = find_rela_by_dest_range(insn->sec, insn->offset,
+-                                             insn->len);
+-              if (!rela) {
+-                      dest_off = insn->offset + insn->len + insn->immediate;
+-                      insn->call_dest = find_symbol_by_offset(insn->sec,
+-                                                              dest_off);
+-                      /*
+-                       * FIXME: Thanks to retpolines, it's now considered
+-                       * normal for a function to call within itself.  So
+-                       * disable this warning for now.
+-                       */
+-#if 0
+-                      if (!insn->call_dest) {
+-                              WARN_FUNC("can't find call dest symbol at offset 0x%lx",
+-                                        insn->sec, insn->offset, dest_off);
+-                              return -1;
+-                      }
+-#endif
+-              } else if (rela->sym->type == STT_SECTION) {
+-                      insn->call_dest = find_symbol_by_offset(rela->sym->sec,
+-                                                              rela->addend+4);
+-                      if (!insn->call_dest ||
+-                          insn->call_dest->type != STT_FUNC) {
+-                              WARN_FUNC("can't find call dest symbol at %s+0x%x",
+-                                        insn->sec, insn->offset,
+-                                        rela->sym->sec->name,
+-                                        rela->addend + 4);
+-                              return -1;
+-                      }
+-              } else
+-                      insn->call_dest = rela->sym;
+-      }
+-
+-      return 0;
+-}
+-
+-/*
+- * The .alternatives section requires some extra special care, over and above
+- * what other special sections require:
+- *
+- * 1. Because alternatives are patched in-place, we need to insert a fake jump
+- *    instruction at the end so that validate_branch() skips all the original
+- *    replaced instructions when validating the new instruction path.
+- *
+- * 2. An added wrinkle is that the new instruction length might be zero.  In
+- *    that case the old instructions are replaced with noops.  We simulate that
+- *    by creating a fake jump as the only new instruction.
+- *
+- * 3. In some cases, the alternative section includes an instruction which
+- *    conditionally jumps to the _end_ of the entry.  We have to modify these
+- *    jumps' destinations to point back to .text rather than the end of the
+- *    entry in .altinstr_replacement.
+- *
+- * 4. It has been requested that we don't validate the !POPCNT feature path
+- *    which is a "very very small percentage of machines".
+- */
+-static int handle_group_alt(struct objtool_file *file,
+-                          struct special_alt *special_alt,
+-                          struct instruction *orig_insn,
+-                          struct instruction **new_insn)
+-{
+-      struct instruction *last_orig_insn, *last_new_insn, *insn, *fake_jump;
+-      unsigned long dest_off;
+-
+-      last_orig_insn = NULL;
+-      insn = orig_insn;
+-      sec_for_each_insn_from(file, insn) {
+-              if (insn->offset >= special_alt->orig_off + special_alt->orig_len)
+-                      break;
+-
+-              if (special_alt->skip_orig)
+-                      insn->type = INSN_NOP;
+-
+-              insn->alt_group = true;
+-              last_orig_insn = insn;
+-      }
+-
+-      if (!next_insn_same_sec(file, last_orig_insn)) {
+-              WARN("%s: don't know how to handle alternatives at end of section",
+-                   special_alt->orig_sec->name);
+-              return -1;
+-      }
+-
+-      fake_jump = malloc(sizeof(*fake_jump));
+-      if (!fake_jump) {
+-              WARN("malloc failed");
+-              return -1;
+-      }
+-      memset(fake_jump, 0, sizeof(*fake_jump));
+-      INIT_LIST_HEAD(&fake_jump->alts);
+-      fake_jump->sec = special_alt->new_sec;
+-      fake_jump->offset = -1;
+-      fake_jump->type = INSN_JUMP_UNCONDITIONAL;
+-      fake_jump->jump_dest = list_next_entry(last_orig_insn, list);
+-
+-      if (!special_alt->new_len) {
+-              *new_insn = fake_jump;
+-              return 0;
+-      }
+-
+-      last_new_insn = NULL;
+-      insn = *new_insn;
+-      sec_for_each_insn_from(file, insn) {
+-              if (insn->offset >= special_alt->new_off + special_alt->new_len)
+-                      break;
+-
+-              last_new_insn = insn;
+-
+-              if (insn->type != INSN_JUMP_CONDITIONAL &&
+-                  insn->type != INSN_JUMP_UNCONDITIONAL)
+-                      continue;
+-
+-              if (!insn->immediate)
+-                      continue;
+-
+-              dest_off = insn->offset + insn->len + insn->immediate;
+-              if (dest_off == special_alt->new_off + special_alt->new_len)
+-                      insn->jump_dest = fake_jump;
+-
+-              if (!insn->jump_dest) {
+-                      WARN_FUNC("can't find alternative jump destination",
+-                                insn->sec, insn->offset);
+-                      return -1;
+-              }
+-      }
+-
+-      if (!last_new_insn) {
+-              WARN_FUNC("can't find last new alternative instruction",
+-                        special_alt->new_sec, special_alt->new_off);
+-              return -1;
+-      }
+-
+-      list_add(&fake_jump->list, &last_new_insn->list);
+-
+-      return 0;
+-}
+-
+-/*
+- * A jump table entry can either convert a nop to a jump or a jump to a nop.
+- * If the original instruction is a jump, make the alt entry an effective nop
+- * by just skipping the original instruction.
+- */
+-static int handle_jump_alt(struct objtool_file *file,
+-                         struct special_alt *special_alt,
+-                         struct instruction *orig_insn,
+-                         struct instruction **new_insn)
+-{
+-      if (orig_insn->type == INSN_NOP)
+-              return 0;
+-
+-      if (orig_insn->type != INSN_JUMP_UNCONDITIONAL) {
+-              WARN_FUNC("unsupported instruction at jump label",
+-                        orig_insn->sec, orig_insn->offset);
+-              return -1;
+-      }
+-
+-      *new_insn = list_next_entry(orig_insn, list);
+-      return 0;
+-}
+-
+-/*
+- * Read all the special sections which have alternate instructions which can be
+- * patched in or redirected to at runtime.  Each instruction having alternate
+- * instruction(s) has them added to its insn->alts list, which will be
+- * traversed in validate_branch().
+- */
+-static int add_special_section_alts(struct objtool_file *file)
+-{
+-      struct list_head special_alts;
+-      struct instruction *orig_insn, *new_insn;
+-      struct special_alt *special_alt, *tmp;
+-      struct alternative *alt;
+-      int ret;
++#include "check.h"
+-      ret = special_get_alts(file->elf, &special_alts);
+-      if (ret)
+-              return ret;
++bool nofp;
+-      list_for_each_entry_safe(special_alt, tmp, &special_alts, list) {
+-
+-              orig_insn = find_insn(file, special_alt->orig_sec,
+-                                    special_alt->orig_off);
+-              if (!orig_insn) {
+-                      WARN_FUNC("special: can't find orig instruction",
+-                                special_alt->orig_sec, special_alt->orig_off);
+-                      ret = -1;
+-                      goto out;
+-              }
+-
+-              /* Ignore retpoline alternatives. */
+-              if (orig_insn->ignore_alts)
+-                      continue;
+-
+-              new_insn = NULL;
+-              if (!special_alt->group || special_alt->new_len) {
+-                      new_insn = find_insn(file, special_alt->new_sec,
+-                                           special_alt->new_off);
+-                      if (!new_insn) {
+-                              WARN_FUNC("special: can't find new instruction",
+-                                        special_alt->new_sec,
+-                                        special_alt->new_off);
+-                              ret = -1;
+-                              goto out;
+-                      }
+-              }
+-
+-              if (special_alt->group) {
+-                      ret = handle_group_alt(file, special_alt, orig_insn,
+-                                             &new_insn);
+-                      if (ret)
+-                              goto out;
+-              } else if (special_alt->jump_or_nop) {
+-                      ret = handle_jump_alt(file, special_alt, orig_insn,
+-                                            &new_insn);
+-                      if (ret)
+-                              goto out;
+-              }
+-
+-              alt = malloc(sizeof(*alt));
+-              if (!alt) {
+-                      WARN("malloc failed");
+-                      ret = -1;
+-                      goto out;
+-              }
+-
+-              alt->insn = new_insn;
+-              list_add_tail(&alt->list, &orig_insn->alts);
+-
+-              list_del(&special_alt->list);
+-              free(special_alt);
+-      }
+-
+-out:
+-      return ret;
+-}
+-
+-static int add_switch_table(struct objtool_file *file, struct symbol *func,
+-                          struct instruction *insn, struct rela *table,
+-                          struct rela *next_table)
+-{
+-      struct rela *rela = table;
+-      struct instruction *alt_insn;
+-      struct alternative *alt;
+-
+-      list_for_each_entry_from(rela, &file->rodata->rela->rela_list, list) {
+-              if (rela == next_table)
+-                      break;
+-
+-              if (rela->sym->sec != insn->sec ||
+-                  rela->addend <= func->offset ||
+-                  rela->addend >= func->offset + func->len)
+-                      break;
+-
+-              alt_insn = find_insn(file, insn->sec, rela->addend);
+-              if (!alt_insn) {
+-                      WARN("%s: can't find instruction at %s+0x%x",
+-                           file->rodata->rela->name, insn->sec->name,
+-                           rela->addend);
+-                      return -1;
+-              }
+-
+-              alt = malloc(sizeof(*alt));
+-              if (!alt) {
+-                      WARN("malloc failed");
+-                      return -1;
+-              }
+-
+-              alt->insn = alt_insn;
+-              list_add_tail(&alt->list, &insn->alts);
+-      }
+-
+-      return 0;
+-}
+-
+-/*
+- * find_switch_table() - Given a dynamic jump, find the switch jump table in
+- * .rodata associated with it.
+- *
+- * There are 3 basic patterns:
+- *
+- * 1. jmpq *[rodata addr](,%reg,8)
+- *
+- *    This is the most common case by far.  It jumps to an address in a simple
+- *    jump table which is stored in .rodata.
+- *
+- * 2. jmpq *[rodata addr](%rip)
+- *
+- *    This is caused by a rare GCC quirk, currently only seen in three driver
+- *    functions in the kernel, only with certain obscure non-distro configs.
+- *
+- *    As part of an optimization, GCC makes a copy of an existing switch jump
+- *    table, modifies it, and then hard-codes the jump (albeit with an indirect
+- *    jump) to use a single entry in the table.  The rest of the jump table and
+- *    some of its jump targets remain as dead code.
+- *
+- *    In such a case we can just crudely ignore all unreachable instruction
+- *    warnings for the entire object file.  Ideally we would just ignore them
+- *    for the function, but that would require redesigning the code quite a
+- *    bit.  And honestly that's just not worth doing: unreachable instruction
+- *    warnings are of questionable value anyway, and this is such a rare issue.
+- *
+- * 3. mov [rodata addr],%reg1
+- *    ... some instructions ...
+- *    jmpq *(%reg1,%reg2,8)
+- *
+- *    This is a fairly uncommon pattern which is new for GCC 6.  As of this
+- *    writing, there are 11 occurrences of it in the allmodconfig kernel.
+- *
+- *    TODO: Once we have DWARF CFI and smarter instruction decoding logic,
+- *    ensure the same register is used in the mov and jump instructions.
+- */
+-static struct rela *find_switch_table(struct objtool_file *file,
+-                                    struct symbol *func,
+-                                    struct instruction *insn)
+-{
+-      struct rela *text_rela, *rodata_rela;
+-      struct instruction *orig_insn = insn;
+-
+-      text_rela = find_rela_by_dest_range(insn->sec, insn->offset, insn->len);
+-      if (text_rela && text_rela->sym == file->rodata->sym) {
+-              /* case 1 */
+-              rodata_rela = find_rela_by_dest(file->rodata,
+-                                              text_rela->addend);
+-              if (rodata_rela)
+-                      return rodata_rela;
+-
+-              /* case 2 */
+-              rodata_rela = find_rela_by_dest(file->rodata,
+-                                              text_rela->addend + 4);
+-              if (!rodata_rela)
+-                      return NULL;
+-              file->ignore_unreachables = true;
+-              return rodata_rela;
+-      }
+-
+-      /* case 3 */
+-      func_for_each_insn_continue_reverse(file, func, insn) {
+-              if (insn->type == INSN_JUMP_DYNAMIC)
+-                      break;
+-
+-              /* allow small jumps within the range */
+-              if (insn->type == INSN_JUMP_UNCONDITIONAL &&
+-                  insn->jump_dest &&
+-                  (insn->jump_dest->offset <= insn->offset ||
+-                   insn->jump_dest->offset > orig_insn->offset))
+-                  break;
+-
+-              /* look for a relocation which references .rodata */
+-              text_rela = find_rela_by_dest_range(insn->sec, insn->offset,
+-                                                  insn->len);
+-              if (!text_rela || text_rela->sym != file->rodata->sym)
+-                      continue;
+-
+-              /*
+-               * Make sure the .rodata address isn't associated with a
+-               * symbol.  gcc jump tables are anonymous data.
+-               */
+-              if (find_symbol_containing(file->rodata, text_rela->addend))
+-                      continue;
+-
+-              return find_rela_by_dest(file->rodata, text_rela->addend);
+-      }
+-
+-      return NULL;
+-}
+-
+-static int add_func_switch_tables(struct objtool_file *file,
+-                                struct symbol *func)
+-{
+-      struct instruction *insn, *prev_jump = NULL;
+-      struct rela *rela, *prev_rela = NULL;
+-      int ret;
+-
+-      func_for_each_insn(file, func, insn) {
+-              if (insn->type != INSN_JUMP_DYNAMIC)
+-                      continue;
+-
+-              rela = find_switch_table(file, func, insn);
+-              if (!rela)
+-                      continue;
+-
+-              /*
+-               * We found a switch table, but we don't know yet how big it
+-               * is.  Don't add it until we reach the end of the function or
+-               * the beginning of another switch table in the same function.
+-               */
+-              if (prev_jump) {
+-                      ret = add_switch_table(file, func, prev_jump, prev_rela,
+-                                             rela);
+-                      if (ret)
+-                              return ret;
+-              }
+-
+-              prev_jump = insn;
+-              prev_rela = rela;
+-      }
+-
+-      if (prev_jump) {
+-              ret = add_switch_table(file, func, prev_jump, prev_rela, NULL);
+-              if (ret)
+-                      return ret;
+-      }
+-
+-      return 0;
+-}
+-
+-/*
+- * For some switch statements, gcc generates a jump table in the .rodata
+- * section which contains a list of addresses within the function to jump to.
+- * This finds these jump tables and adds them to the insn->alts lists.
+- */
+-static int add_switch_table_alts(struct objtool_file *file)
+-{
+-      struct section *sec;
+-      struct symbol *func;
+-      int ret;
+-
+-      if (!file->rodata || !file->rodata->rela)
+-              return 0;
+-
+-      list_for_each_entry(sec, &file->elf->sections, list) {
+-              list_for_each_entry(func, &sec->symbol_list, list) {
+-                      if (func->type != STT_FUNC)
+-                              continue;
+-
+-                      ret = add_func_switch_tables(file, func);
+-                      if (ret)
+-                              return ret;
+-              }
+-      }
+-
+-      return 0;
+-}
+-
+-static int decode_sections(struct objtool_file *file)
+-{
+-      int ret;
+-
+-      ret = decode_instructions(file);
+-      if (ret)
+-              return ret;
+-
+-      ret = add_dead_ends(file);
+-      if (ret)
+-              return ret;
+-
+-      add_ignores(file);
+-
+-      ret = add_nospec_ignores(file);
+-      if (ret)
+-              return ret;
+-
+-      ret = add_jump_destinations(file);
+-      if (ret)
+-              return ret;
+-
+-      ret = add_call_destinations(file);
+-      if (ret)
+-              return ret;
+-
+-      ret = add_special_section_alts(file);
+-      if (ret)
+-              return ret;
+-
+-      ret = add_switch_table_alts(file);
+-      if (ret)
+-              return ret;
+-
+-      return 0;
+-}
+-
+-static bool is_fentry_call(struct instruction *insn)
+-{
+-      if (insn->type == INSN_CALL &&
+-          insn->call_dest->type == STT_NOTYPE &&
+-          !strcmp(insn->call_dest->name, "__fentry__"))
+-              return true;
+-
+-      return false;
+-}
+-
+-static bool has_modified_stack_frame(struct instruction *insn)
+-{
+-      return (insn->state & STATE_FP_SAVED) ||
+-             (insn->state & STATE_FP_SETUP);
+-}
+-
+-static bool has_valid_stack_frame(struct instruction *insn)
+-{
+-      return (insn->state & STATE_FP_SAVED) &&
+-             (insn->state & STATE_FP_SETUP);
+-}
+-
+-static unsigned int frame_state(unsigned long state)
+-{
+-      return (state & (STATE_FP_SAVED | STATE_FP_SETUP));
+-}
+-
+-/*
+- * Follow the branch starting at the given instruction, and recursively follow
+- * any other branches (jumps).  Meanwhile, track the frame pointer state at
+- * each instruction and validate all the rules described in
+- * tools/objtool/Documentation/stack-validation.txt.
+- */
+-static int validate_branch(struct objtool_file *file,
+-                         struct instruction *first, unsigned char first_state)
+-{
+-      struct alternative *alt;
+-      struct instruction *insn;
+-      struct section *sec;
+-      struct symbol *func = NULL;
+-      unsigned char state;
+-      int ret;
+-
+-      insn = first;
+-      sec = insn->sec;
+-      state = first_state;
+-
+-      if (insn->alt_group && list_empty(&insn->alts)) {
+-              WARN_FUNC("don't know how to handle branch to middle of alternative instruction group",
+-                        sec, insn->offset);
+-              return 1;
+-      }
+-
+-      while (1) {
+-              if (file->c_file && insn->func) {
+-                      if (func && func != insn->func) {
+-                              WARN("%s() falls through to next function %s()",
+-                                   func->name, insn->func->name);
+-                              return 1;
+-                      }
+-
+-                      func = insn->func;
+-              }
+-
+-              if (insn->visited) {
+-                      if (frame_state(insn->state) != frame_state(state)) {
+-                              WARN_FUNC("frame pointer state mismatch",
+-                                        sec, insn->offset);
+-                              return 1;
+-                      }
+-
+-                      return 0;
+-              }
+-
+-              insn->visited = true;
+-              insn->state = state;
+-
+-              list_for_each_entry(alt, &insn->alts, list) {
+-                      ret = validate_branch(file, alt->insn, state);
+-                      if (ret)
+-                              return 1;
+-              }
+-
+-              switch (insn->type) {
+-
+-              case INSN_FP_SAVE:
+-                      if (!nofp) {
+-                              if (state & STATE_FP_SAVED) {
+-                                      WARN_FUNC("duplicate frame pointer save",
+-                                                sec, insn->offset);
+-                                      return 1;
+-                              }
+-                              state |= STATE_FP_SAVED;
+-                      }
+-                      break;
+-
+-              case INSN_FP_SETUP:
+-                      if (!nofp) {
+-                              if (state & STATE_FP_SETUP) {
+-                                      WARN_FUNC("duplicate frame pointer setup",
+-                                                sec, insn->offset);
+-                                      return 1;
+-                              }
+-                              state |= STATE_FP_SETUP;
+-                      }
+-                      break;
+-
+-              case INSN_FP_RESTORE:
+-                      if (!nofp) {
+-                              if (has_valid_stack_frame(insn))
+-                                      state &= ~STATE_FP_SETUP;
+-
+-                              state &= ~STATE_FP_SAVED;
+-                      }
+-                      break;
+-
+-              case INSN_RETURN:
+-                      if (!nofp && has_modified_stack_frame(insn)) {
+-                              WARN_FUNC("return without frame pointer restore",
+-                                        sec, insn->offset);
+-                              return 1;
+-                      }
+-                      return 0;
+-
+-              case INSN_CALL:
+-                      if (is_fentry_call(insn)) {
+-                              state |= STATE_FENTRY;
+-                              break;
+-                      }
+-
+-                      ret = dead_end_function(file, insn->call_dest);
+-                      if (ret == 1)
+-                              return 0;
+-                      if (ret == -1)
+-                              return 1;
+-
+-                      /* fallthrough */
+-              case INSN_CALL_DYNAMIC:
+-                      if (!nofp && !has_valid_stack_frame(insn)) {
+-                              WARN_FUNC("call without frame pointer save/setup",
+-                                        sec, insn->offset);
+-                              return 1;
+-                      }
+-                      break;
+-
+-              case INSN_JUMP_CONDITIONAL:
+-              case INSN_JUMP_UNCONDITIONAL:
+-                      if (insn->jump_dest) {
+-                              ret = validate_branch(file, insn->jump_dest,
+-                                                    state);
+-                              if (ret)
+-                                      return 1;
+-                      } else if (has_modified_stack_frame(insn)) {
+-                              WARN_FUNC("sibling call from callable instruction with changed frame pointer",
+-                                        sec, insn->offset);
+-                              return 1;
+-                      } /* else it's a sibling call */
+-
+-                      if (insn->type == INSN_JUMP_UNCONDITIONAL)
+-                              return 0;
+-
+-                      break;
+-
+-              case INSN_JUMP_DYNAMIC:
+-                      if (list_empty(&insn->alts) &&
+-                          has_modified_stack_frame(insn)) {
+-                              WARN_FUNC("sibling call from callable instruction with changed frame pointer",
+-                                        sec, insn->offset);
+-                              return 1;
+-                      }
+-
+-                      return 0;
+-
+-              default:
+-                      break;
+-              }
+-
+-              if (insn->dead_end)
+-                      return 0;
+-
+-              insn = next_insn_same_sec(file, insn);
+-              if (!insn) {
+-                      WARN("%s: unexpected end of section", sec->name);
+-                      return 1;
+-              }
+-      }
+-
+-      return 0;
+-}
+-
+-static bool is_kasan_insn(struct instruction *insn)
+-{
+-      return (insn->type == INSN_CALL &&
+-              !strcmp(insn->call_dest->name, "__asan_handle_no_return"));
+-}
+-
+-static bool is_ubsan_insn(struct instruction *insn)
+-{
+-      return (insn->type == INSN_CALL &&
+-              !strcmp(insn->call_dest->name,
+-                      "__ubsan_handle_builtin_unreachable"));
+-}
+-
+-static bool ignore_unreachable_insn(struct symbol *func,
+-                                  struct instruction *insn)
+-{
+-      int i;
+-
+-      if (insn->type == INSN_NOP)
+-              return true;
+-
+-      /*
+-       * Check if this (or a subsequent) instruction is related to
+-       * CONFIG_UBSAN or CONFIG_KASAN.
+-       *
+-       * End the search at 5 instructions to avoid going into the weeds.
+-       */
+-      for (i = 0; i < 5; i++) {
+-
+-              if (is_kasan_insn(insn) || is_ubsan_insn(insn))
+-                      return true;
+-
+-              if (insn->type == INSN_JUMP_UNCONDITIONAL && insn->jump_dest) {
+-                      insn = insn->jump_dest;
+-                      continue;
+-              }
+-
+-              if (insn->offset + insn->len >= func->offset + func->len)
+-                      break;
+-              insn = list_next_entry(insn, list);
+-      }
+-
+-      return false;
+-}
+-
+-static int validate_functions(struct objtool_file *file)
+-{
+-      struct section *sec;
+-      struct symbol *func;
+-      struct instruction *insn;
+-      int ret, warnings = 0;
+-
+-      list_for_each_entry(sec, &file->elf->sections, list) {
+-              list_for_each_entry(func, &sec->symbol_list, list) {
+-                      if (func->type != STT_FUNC)
+-                              continue;
+-
+-                      insn = find_insn(file, sec, func->offset);
+-                      if (!insn)
+-                              continue;
+-
+-                      ret = validate_branch(file, insn, 0);
+-                      warnings += ret;
+-              }
+-      }
+-
+-      list_for_each_entry(sec, &file->elf->sections, list) {
+-              list_for_each_entry(func, &sec->symbol_list, list) {
+-                      if (func->type != STT_FUNC)
+-                              continue;
+-
+-                      func_for_each_insn(file, func, insn) {
+-                              if (insn->visited)
+-                                      continue;
+-
+-                              insn->visited = true;
+-
+-                              if (file->ignore_unreachables || warnings ||
+-                                  ignore_unreachable_insn(func, insn))
+-                                      continue;
+-
+-                              /*
+-                               * gcov produces a lot of unreachable
+-                               * instructions.  If we get an unreachable
+-                               * warning and the file has gcov enabled, just
+-                               * ignore it, and all other such warnings for
+-                               * the file.
+-                               */
+-                              if (!file->ignore_unreachables &&
+-                                  gcov_enabled(file)) {
+-                                      file->ignore_unreachables = true;
+-                                      continue;
+-                              }
+-
+-                              WARN_FUNC("function has unreachable instruction", insn->sec, insn->offset);
+-                              warnings++;
+-                      }
+-              }
+-      }
+-
+-      return warnings;
+-}
+-
+-static int validate_uncallable_instructions(struct objtool_file *file)
+-{
+-      struct instruction *insn;
+-      int warnings = 0;
+-
+-      for_each_insn(file, insn) {
+-              if (!insn->visited && insn->type == INSN_RETURN) {
+-
+-                      /*
+-                       * Don't warn about call instructions in unvisited
+-                       * retpoline alternatives.
+-                       */
+-                      if (!strcmp(insn->sec->name, ".altinstr_replacement"))
+-                              continue;
+-
+-                      WARN_FUNC("return instruction outside of a callable function",
+-                                insn->sec, insn->offset);
+-                      warnings++;
+-              }
+-      }
+-
+-      return warnings;
+-}
+-
+-static void cleanup(struct objtool_file *file)
+-{
+-      struct instruction *insn, *tmpinsn;
+-      struct alternative *alt, *tmpalt;
+-
+-      list_for_each_entry_safe(insn, tmpinsn, &file->insn_list, list) {
+-              list_for_each_entry_safe(alt, tmpalt, &insn->alts, list) {
+-                      list_del(&alt->list);
+-                      free(alt);
+-              }
+-              list_del(&insn->list);
+-              hash_del(&insn->hash);
+-              free(insn);
+-      }
+-      elf_close(file->elf);
+-}
+-
+-const char * const check_usage[] = {
++static const char * const check_usage[] = {
+       "objtool check [<options>] file.o",
+       NULL,
+ };
++const struct option check_options[] = {
++      OPT_BOOLEAN('f', "no-fp", &nofp, "Skip frame pointer validation"),
++      OPT_END(),
++};
++
+ int cmd_check(int argc, const char **argv)
+ {
+-      struct objtool_file file;
+-      int ret, warnings = 0;
++      const char *objname;
+-      const struct option options[] = {
+-              OPT_BOOLEAN('f', "no-fp", &nofp, "Skip frame pointer validation"),
+-              OPT_END(),
+-      };
+-
+-      argc = parse_options(argc, argv, options, check_usage, 0);
++      argc = parse_options(argc, argv, check_options, check_usage, 0);
+       if (argc != 1)
+-              usage_with_options(check_usage, options);
++              usage_with_options(check_usage, check_options);
+       objname = argv[0];
+-      file.elf = elf_open(objname);
+-      if (!file.elf) {
+-              fprintf(stderr, "error reading elf file %s\n", objname);
+-              return 1;
+-      }
+-
+-      INIT_LIST_HEAD(&file.insn_list);
+-      hash_init(file.insn_hash);
+-      file.whitelist = find_section_by_name(file.elf, ".discard.func_stack_frame_non_standard");
+-      file.rodata = find_section_by_name(file.elf, ".rodata");
+-      file.ignore_unreachables = false;
+-      file.c_file = find_section_by_name(file.elf, ".comment");
+-
+-      ret = decode_sections(&file);
+-      if (ret < 0)
+-              goto out;
+-      warnings += ret;
+-
+-      ret = validate_functions(&file);
+-      if (ret < 0)
+-              goto out;
+-      warnings += ret;
+-
+-      ret = validate_uncallable_instructions(&file);
+-      if (ret < 0)
+-              goto out;
+-      warnings += ret;
+-
+-out:
+-      cleanup(&file);
+-
+-      /* ignore warnings for now until we get all the code cleaned up */
+-      if (ret || warnings)
+-              return 0;
+-      return 0;
++      return check(objname, nofp);
+ }
+--- /dev/null
++++ b/tools/objtool/check.c
+@@ -0,0 +1,1327 @@
++/*
++ * Copyright (C) 2015-2017 Josh Poimboeuf <jpoimboe@redhat.com>
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation; either version 2
++ * of the License, or (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, see <http://www.gnu.org/licenses/>.
++ */
++
++#include <string.h>
++#include <stdlib.h>
++
++#include "check.h"
++#include "elf.h"
++#include "special.h"
++#include "arch.h"
++#include "warn.h"
++
++#include <linux/hashtable.h>
++
++#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
++
++#define STATE_FP_SAVED                0x1
++#define STATE_FP_SETUP                0x2
++#define STATE_FENTRY          0x4
++
++struct alternative {
++      struct list_head list;
++      struct instruction *insn;
++};
++
++const char *objname;
++static bool nofp;
++
++static struct instruction *find_insn(struct objtool_file *file,
++                                   struct section *sec, unsigned long offset)
++{
++      struct instruction *insn;
++
++      hash_for_each_possible(file->insn_hash, insn, hash, offset)
++              if (insn->sec == sec && insn->offset == offset)
++                      return insn;
++
++      return NULL;
++}
++
++static struct instruction *next_insn_same_sec(struct objtool_file *file,
++                                            struct instruction *insn)
++{
++      struct instruction *next = list_next_entry(insn, list);
++
++      if (&next->list == &file->insn_list || next->sec != insn->sec)
++              return NULL;
++
++      return next;
++}
++
++static bool gcov_enabled(struct objtool_file *file)
++{
++      struct section *sec;
++      struct symbol *sym;
++
++      list_for_each_entry(sec, &file->elf->sections, list)
++              list_for_each_entry(sym, &sec->symbol_list, list)
++                      if (!strncmp(sym->name, "__gcov_.", 8))
++                              return true;
++
++      return false;
++}
++
++#define for_each_insn(file, insn)                                     \
++      list_for_each_entry(insn, &file->insn_list, list)
++
++#define func_for_each_insn(file, func, insn)                          \
++      for (insn = find_insn(file, func->sec, func->offset);           \
++           insn && &insn->list != &file->insn_list &&                 \
++              insn->sec == func->sec &&                               \
++              insn->offset < func->offset + func->len;                \
++           insn = list_next_entry(insn, list))
++
++#define func_for_each_insn_continue_reverse(file, func, insn)         \
++      for (insn = list_prev_entry(insn, list);                        \
++           &insn->list != &file->insn_list &&                         \
++              insn->sec == func->sec && insn->offset >= func->offset; \
++           insn = list_prev_entry(insn, list))
++
++#define sec_for_each_insn_from(file, insn)                            \
++      for (; insn; insn = next_insn_same_sec(file, insn))
++
++
++/*
++ * Check if the function has been manually whitelisted with the
++ * STACK_FRAME_NON_STANDARD macro, or if it should be automatically whitelisted
++ * due to its use of a context switching instruction.
++ */
++static bool ignore_func(struct objtool_file *file, struct symbol *func)
++{
++      struct rela *rela;
++      struct instruction *insn;
++
++      /* check for STACK_FRAME_NON_STANDARD */
++      if (file->whitelist && file->whitelist->rela)
++              list_for_each_entry(rela, &file->whitelist->rela->rela_list, list) {
++                      if (rela->sym->type == STT_SECTION &&
++                          rela->sym->sec == func->sec &&
++                          rela->addend == func->offset)
++                              return true;
++                      if (rela->sym->type == STT_FUNC && rela->sym == func)
++                              return true;
++              }
++
++      /* check if it has a context switching instruction */
++      func_for_each_insn(file, func, insn)
++              if (insn->type == INSN_CONTEXT_SWITCH)
++                      return true;
++
++      return false;
++}
++
++/*
++ * This checks to see if the given function is a "noreturn" function.
++ *
++ * For global functions which are outside the scope of this object file, we
++ * have to keep a manual list of them.
++ *
++ * For local functions, we have to detect them manually by simply looking for
++ * the lack of a return instruction.
++ *
++ * Returns:
++ *  -1: error
++ *   0: no dead end
++ *   1: dead end
++ */
++static int __dead_end_function(struct objtool_file *file, struct symbol *func,
++                             int recursion)
++{
++      int i;
++      struct instruction *insn;
++      bool empty = true;
++
++      /*
++       * Unfortunately these have to be hard coded because the noreturn
++       * attribute isn't provided in ELF data.
++       */
++      static const char * const global_noreturns[] = {
++              "__stack_chk_fail",
++              "panic",
++              "do_exit",
++              "do_task_dead",
++              "__module_put_and_exit",
++              "complete_and_exit",
++              "kvm_spurious_fault",
++              "__reiserfs_panic",
++              "lbug_with_loc"
++      };
++
++      if (func->bind == STB_WEAK)
++              return 0;
++
++      if (func->bind == STB_GLOBAL)
++              for (i = 0; i < ARRAY_SIZE(global_noreturns); i++)
++                      if (!strcmp(func->name, global_noreturns[i]))
++                              return 1;
++
++      if (!func->sec)
++              return 0;
++
++      func_for_each_insn(file, func, insn) {
++              empty = false;
++
++              if (insn->type == INSN_RETURN)
++                      return 0;
++      }
++
++      if (empty)
++              return 0;
++
++      /*
++       * A function can have a sibling call instead of a return.  In that
++       * case, the function's dead-end status depends on whether the target
++       * of the sibling call returns.
++       */
++      func_for_each_insn(file, func, insn) {
++              if (insn->sec != func->sec ||
++                  insn->offset >= func->offset + func->len)
++                      break;
++
++              if (insn->type == INSN_JUMP_UNCONDITIONAL) {
++                      struct instruction *dest = insn->jump_dest;
++                      struct symbol *dest_func;
++
++                      if (!dest)
++                              /* sibling call to another file */
++                              return 0;
++
++                      if (dest->sec != func->sec ||
++                          dest->offset < func->offset ||
++                          dest->offset >= func->offset + func->len) {
++                              /* local sibling call */
++                              dest_func = find_symbol_by_offset(dest->sec,
++                                                                dest->offset);
++                              if (!dest_func)
++                                      continue;
++
++                              if (recursion == 5) {
++                                      WARN_FUNC("infinite recursion (objtool bug!)",
++                                                dest->sec, dest->offset);
++                                      return -1;
++                              }
++
++                              return __dead_end_function(file, dest_func,
++                                                         recursion + 1);
++                      }
++              }
++
++              if (insn->type == INSN_JUMP_DYNAMIC && list_empty(&insn->alts))
++                      /* sibling call */
++                      return 0;
++      }
++
++      return 1;
++}
++
++static int dead_end_function(struct objtool_file *file, struct symbol *func)
++{
++      return __dead_end_function(file, func, 0);
++}
++
++/*
++ * Call the arch-specific instruction decoder for all the instructions and add
++ * them to the global instruction list.
++ */
++static int decode_instructions(struct objtool_file *file)
++{
++      struct section *sec;
++      struct symbol *func;
++      unsigned long offset;
++      struct instruction *insn;
++      int ret;
++
++      list_for_each_entry(sec, &file->elf->sections, list) {
++
++              if (!(sec->sh.sh_flags & SHF_EXECINSTR))
++                      continue;
++
++              for (offset = 0; offset < sec->len; offset += insn->len) {
++                      insn = malloc(sizeof(*insn));
++                      memset(insn, 0, sizeof(*insn));
++
++                      INIT_LIST_HEAD(&insn->alts);
++                      insn->sec = sec;
++                      insn->offset = offset;
++
++                      ret = arch_decode_instruction(file->elf, sec, offset,
++                                                    sec->len - offset,
++                                                    &insn->len, &insn->type,
++                                                    &insn->immediate);
++                      if (ret)
++                              return ret;
++
++                      if (!insn->type || insn->type > INSN_LAST) {
++                              WARN_FUNC("invalid instruction type %d",
++                                        insn->sec, insn->offset, insn->type);
++                              return -1;
++                      }
++
++                      hash_add(file->insn_hash, &insn->hash, insn->offset);
++                      list_add_tail(&insn->list, &file->insn_list);
++              }
++
++              list_for_each_entry(func, &sec->symbol_list, list) {
++                      if (func->type != STT_FUNC)
++                              continue;
++
++                      if (!find_insn(file, sec, func->offset)) {
++                              WARN("%s(): can't find starting instruction",
++                                   func->name);
++                              return -1;
++                      }
++
++                      func_for_each_insn(file, func, insn)
++                              if (!insn->func)
++                                      insn->func = func;
++              }
++      }
++
++      return 0;
++}
++
++/*
++ * Find all uses of the unreachable() macro, which are code path dead ends.
++ */
++static int add_dead_ends(struct objtool_file *file)
++{
++      struct section *sec;
++      struct rela *rela;
++      struct instruction *insn;
++      bool found;
++
++      sec = find_section_by_name(file->elf, ".rela__unreachable");
++      if (!sec)
++              return 0;
++
++      list_for_each_entry(rela, &sec->rela_list, list) {
++              if (rela->sym->type != STT_SECTION) {
++                      WARN("unexpected relocation symbol type in .rela__unreachable");
++                      return -1;
++              }
++              insn = find_insn(file, rela->sym->sec, rela->addend);
++              if (insn)
++                      insn = list_prev_entry(insn, list);
++              else if (rela->addend == rela->sym->sec->len) {
++                      found = false;
++                      list_for_each_entry_reverse(insn, &file->insn_list, list) {
++                              if (insn->sec == rela->sym->sec) {
++                                      found = true;
++                                      break;
++                              }
++                      }
++
++                      if (!found) {
++                              WARN("can't find unreachable insn at %s+0x%x",
++                                   rela->sym->sec->name, rela->addend);
++                              return -1;
++                      }
++              } else {
++                      WARN("can't find unreachable insn at %s+0x%x",
++                           rela->sym->sec->name, rela->addend);
++                      return -1;
++              }
++
++              insn->dead_end = true;
++      }
++
++      return 0;
++}
++
++/*
++ * Warnings shouldn't be reported for ignored functions.
++ */
++static void add_ignores(struct objtool_file *file)
++{
++      struct instruction *insn;
++      struct section *sec;
++      struct symbol *func;
++
++      list_for_each_entry(sec, &file->elf->sections, list) {
++              list_for_each_entry(func, &sec->symbol_list, list) {
++                      if (func->type != STT_FUNC)
++                              continue;
++
++                      if (!ignore_func(file, func))
++                              continue;
++
++                      func_for_each_insn(file, func, insn)
++                              insn->visited = true;
++              }
++      }
++}
++
++/*
++ * FIXME: For now, just ignore any alternatives which add retpolines.  This is
++ * a temporary hack, as it doesn't allow ORC to unwind from inside a retpoline.
++ * But it at least allows objtool to understand the control flow *around* the
++ * retpoline.
++ */
++static int add_nospec_ignores(struct objtool_file *file)
++{
++      struct section *sec;
++      struct rela *rela;
++      struct instruction *insn;
++
++      sec = find_section_by_name(file->elf, ".rela.discard.nospec");
++      if (!sec)
++              return 0;
++
++      list_for_each_entry(rela, &sec->rela_list, list) {
++              if (rela->sym->type != STT_SECTION) {
++                      WARN("unexpected relocation symbol type in %s", sec->name);
++                      return -1;
++              }
++
++              insn = find_insn(file, rela->sym->sec, rela->addend);
++              if (!insn) {
++                      WARN("bad .discard.nospec entry");
++                      return -1;
++              }
++
++              insn->ignore_alts = true;
++      }
++
++      return 0;
++}
++
++/*
++ * Find the destination instructions for all jumps.
++ */
++static int add_jump_destinations(struct objtool_file *file)
++{
++      struct instruction *insn;
++      struct rela *rela;
++      struct section *dest_sec;
++      unsigned long dest_off;
++
++      for_each_insn(file, insn) {
++              if (insn->type != INSN_JUMP_CONDITIONAL &&
++                  insn->type != INSN_JUMP_UNCONDITIONAL)
++                      continue;
++
++              /* skip ignores */
++              if (insn->visited)
++                      continue;
++
++              rela = find_rela_by_dest_range(insn->sec, insn->offset,
++                                             insn->len);
++              if (!rela) {
++                      dest_sec = insn->sec;
++                      dest_off = insn->offset + insn->len + insn->immediate;
++              } else if (rela->sym->type == STT_SECTION) {
++                      dest_sec = rela->sym->sec;
++                      dest_off = rela->addend + 4;
++              } else if (rela->sym->sec->idx) {
++                      dest_sec = rela->sym->sec;
++                      dest_off = rela->sym->sym.st_value + rela->addend + 4;
++              } else if (strstr(rela->sym->name, "_indirect_thunk_")) {
++                      /*
++                       * Retpoline jumps are really dynamic jumps in
++                       * disguise, so convert them accordingly.
++                       */
++                      insn->type = INSN_JUMP_DYNAMIC;
++                      continue;
++              } else {
++                      /* sibling call */
++                      insn->jump_dest = 0;
++                      continue;
++              }
++
++              insn->jump_dest = find_insn(file, dest_sec, dest_off);
++              if (!insn->jump_dest) {
++
++                      /*
++                       * This is a special case where an alt instruction
++                       * jumps past the end of the section.  These are
++                       * handled later in handle_group_alt().
++                       */
++                      if (!strcmp(insn->sec->name, ".altinstr_replacement"))
++                              continue;
++
++                      WARN_FUNC("can't find jump dest instruction at %s+0x%lx",
++                                insn->sec, insn->offset, dest_sec->name,
++                                dest_off);
++                      return -1;
++              }
++      }
++
++      return 0;
++}
++
++/*
++ * Find the destination instructions for all calls.
++ */
++static int add_call_destinations(struct objtool_file *file)
++{
++      struct instruction *insn;
++      unsigned long dest_off;
++      struct rela *rela;
++
++      for_each_insn(file, insn) {
++              if (insn->type != INSN_CALL)
++                      continue;
++
++              rela = find_rela_by_dest_range(insn->sec, insn->offset,
++                                             insn->len);
++              if (!rela) {
++                      dest_off = insn->offset + insn->len + insn->immediate;
++                      insn->call_dest = find_symbol_by_offset(insn->sec,
++                                                              dest_off);
++                      /*
++                       * FIXME: Thanks to retpolines, it's now considered
++                       * normal for a function to call within itself.  So
++                       * disable this warning for now.
++                       */
++#if 0
++                      if (!insn->call_dest) {
++                              WARN_FUNC("can't find call dest symbol at offset 0x%lx",
++                                        insn->sec, insn->offset, dest_off);
++                              return -1;
++                      }
++#endif
++              } else if (rela->sym->type == STT_SECTION) {
++                      insn->call_dest = find_symbol_by_offset(rela->sym->sec,
++                                                              rela->addend+4);
++                      if (!insn->call_dest ||
++                          insn->call_dest->type != STT_FUNC) {
++                              WARN_FUNC("can't find call dest symbol at %s+0x%x",
++                                        insn->sec, insn->offset,
++                                        rela->sym->sec->name,
++                                        rela->addend + 4);
++                              return -1;
++                      }
++              } else
++                      insn->call_dest = rela->sym;
++      }
++
++      return 0;
++}
++
++/*
++ * The .alternatives section requires some extra special care, over and above
++ * what other special sections require:
++ *
++ * 1. Because alternatives are patched in-place, we need to insert a fake jump
++ *    instruction at the end so that validate_branch() skips all the original
++ *    replaced instructions when validating the new instruction path.
++ *
++ * 2. An added wrinkle is that the new instruction length might be zero.  In
++ *    that case the old instructions are replaced with noops.  We simulate that
++ *    by creating a fake jump as the only new instruction.
++ *
++ * 3. In some cases, the alternative section includes an instruction which
++ *    conditionally jumps to the _end_ of the entry.  We have to modify these
++ *    jumps' destinations to point back to .text rather than the end of the
++ *    entry in .altinstr_replacement.
++ *
++ * 4. It has been requested that we don't validate the !POPCNT feature path
++ *    which is a "very very small percentage of machines".
++ */
++static int handle_group_alt(struct objtool_file *file,
++                          struct special_alt *special_alt,
++                          struct instruction *orig_insn,
++                          struct instruction **new_insn)
++{
++      struct instruction *last_orig_insn, *last_new_insn, *insn, *fake_jump;
++      unsigned long dest_off;
++
++      last_orig_insn = NULL;
++      insn = orig_insn;
++      sec_for_each_insn_from(file, insn) {
++              if (insn->offset >= special_alt->orig_off + special_alt->orig_len)
++                      break;
++
++              if (special_alt->skip_orig)
++                      insn->type = INSN_NOP;
++
++              insn->alt_group = true;
++              last_orig_insn = insn;
++      }
++
++      if (!next_insn_same_sec(file, last_orig_insn)) {
++              WARN("%s: don't know how to handle alternatives at end of section",
++                   special_alt->orig_sec->name);
++              return -1;
++      }
++
++      fake_jump = malloc(sizeof(*fake_jump));
++      if (!fake_jump) {
++              WARN("malloc failed");
++              return -1;
++      }
++      memset(fake_jump, 0, sizeof(*fake_jump));
++      INIT_LIST_HEAD(&fake_jump->alts);
++      fake_jump->sec = special_alt->new_sec;
++      fake_jump->offset = -1;
++      fake_jump->type = INSN_JUMP_UNCONDITIONAL;
++      fake_jump->jump_dest = list_next_entry(last_orig_insn, list);
++
++      if (!special_alt->new_len) {
++              *new_insn = fake_jump;
++              return 0;
++      }
++
++      last_new_insn = NULL;
++      insn = *new_insn;
++      sec_for_each_insn_from(file, insn) {
++              if (insn->offset >= special_alt->new_off + special_alt->new_len)
++                      break;
++
++              last_new_insn = insn;
++
++              if (insn->type != INSN_JUMP_CONDITIONAL &&
++                  insn->type != INSN_JUMP_UNCONDITIONAL)
++                      continue;
++
++              if (!insn->immediate)
++                      continue;
++
++              dest_off = insn->offset + insn->len + insn->immediate;
++              if (dest_off == special_alt->new_off + special_alt->new_len)
++                      insn->jump_dest = fake_jump;
++
++              if (!insn->jump_dest) {
++                      WARN_FUNC("can't find alternative jump destination",
++                                insn->sec, insn->offset);
++                      return -1;
++              }
++      }
++
++      if (!last_new_insn) {
++              WARN_FUNC("can't find last new alternative instruction",
++                        special_alt->new_sec, special_alt->new_off);
++              return -1;
++      }
++
++      list_add(&fake_jump->list, &last_new_insn->list);
++
++      return 0;
++}
++
++/*
++ * A jump table entry can either convert a nop to a jump or a jump to a nop.
++ * If the original instruction is a jump, make the alt entry an effective nop
++ * by just skipping the original instruction.
++ */
++static int handle_jump_alt(struct objtool_file *file,
++                         struct special_alt *special_alt,
++                         struct instruction *orig_insn,
++                         struct instruction **new_insn)
++{
++      if (orig_insn->type == INSN_NOP)
++              return 0;
++
++      if (orig_insn->type != INSN_JUMP_UNCONDITIONAL) {
++              WARN_FUNC("unsupported instruction at jump label",
++                        orig_insn->sec, orig_insn->offset);
++              return -1;
++      }
++
++      *new_insn = list_next_entry(orig_insn, list);
++      return 0;
++}
++
++/*
++ * Read all the special sections which have alternate instructions which can be
++ * patched in or redirected to at runtime.  Each instruction having alternate
++ * instruction(s) has them added to its insn->alts list, which will be
++ * traversed in validate_branch().
++ */
++static int add_special_section_alts(struct objtool_file *file)
++{
++      struct list_head special_alts;
++      struct instruction *orig_insn, *new_insn;
++      struct special_alt *special_alt, *tmp;
++      struct alternative *alt;
++      int ret;
++
++      ret = special_get_alts(file->elf, &special_alts);
++      if (ret)
++              return ret;
++
++      list_for_each_entry_safe(special_alt, tmp, &special_alts, list) {
++              orig_insn = find_insn(file, special_alt->orig_sec,
++                                    special_alt->orig_off);
++              if (!orig_insn) {
++                      WARN_FUNC("special: can't find orig instruction",
++                                special_alt->orig_sec, special_alt->orig_off);
++                      ret = -1;
++                      goto out;
++              }
++
++              /* Ignore retpoline alternatives. */
++              if (orig_insn->ignore_alts)
++                      continue;
++
++              new_insn = NULL;
++              if (!special_alt->group || special_alt->new_len) {
++                      new_insn = find_insn(file, special_alt->new_sec,
++                                           special_alt->new_off);
++                      if (!new_insn) {
++                              WARN_FUNC("special: can't find new instruction",
++                                        special_alt->new_sec,
++                                        special_alt->new_off);
++                              ret = -1;
++                              goto out;
++                      }
++              }
++
++              if (special_alt->group) {
++                      ret = handle_group_alt(file, special_alt, orig_insn,
++                                             &new_insn);
++                      if (ret)
++                              goto out;
++              } else if (special_alt->jump_or_nop) {
++                      ret = handle_jump_alt(file, special_alt, orig_insn,
++                                            &new_insn);
++                      if (ret)
++                              goto out;
++              }
++
++              alt = malloc(sizeof(*alt));
++              if (!alt) {
++                      WARN("malloc failed");
++                      ret = -1;
++                      goto out;
++              }
++
++              alt->insn = new_insn;
++              list_add_tail(&alt->list, &orig_insn->alts);
++
++              list_del(&special_alt->list);
++              free(special_alt);
++      }
++
++out:
++      return ret;
++}
++
++static int add_switch_table(struct objtool_file *file, struct symbol *func,
++                          struct instruction *insn, struct rela *table,
++                          struct rela *next_table)
++{
++      struct rela *rela = table;
++      struct instruction *alt_insn;
++      struct alternative *alt;
++
++      list_for_each_entry_from(rela, &file->rodata->rela->rela_list, list) {
++              if (rela == next_table)
++                      break;
++
++              if (rela->sym->sec != insn->sec ||
++                  rela->addend <= func->offset ||
++                  rela->addend >= func->offset + func->len)
++                      break;
++
++              alt_insn = find_insn(file, insn->sec, rela->addend);
++              if (!alt_insn) {
++                      WARN("%s: can't find instruction at %s+0x%x",
++                           file->rodata->rela->name, insn->sec->name,
++                           rela->addend);
++                      return -1;
++              }
++
++              alt = malloc(sizeof(*alt));
++              if (!alt) {
++                      WARN("malloc failed");
++                      return -1;
++              }
++
++              alt->insn = alt_insn;
++              list_add_tail(&alt->list, &insn->alts);
++      }
++
++      return 0;
++}
++
++/*
++ * find_switch_table() - Given a dynamic jump, find the switch jump table in
++ * .rodata associated with it.
++ *
++ * There are 3 basic patterns:
++ *
++ * 1. jmpq *[rodata addr](,%reg,8)
++ *
++ *    This is the most common case by far.  It jumps to an address in a simple
++ *    jump table which is stored in .rodata.
++ *
++ * 2. jmpq *[rodata addr](%rip)
++ *
++ *    This is caused by a rare GCC quirk, currently only seen in three driver
++ *    functions in the kernel, only with certain obscure non-distro configs.
++ *
++ *    As part of an optimization, GCC makes a copy of an existing switch jump
++ *    table, modifies it, and then hard-codes the jump (albeit with an indirect
++ *    jump) to use a single entry in the table.  The rest of the jump table and
++ *    some of its jump targets remain as dead code.
++ *
++ *    In such a case we can just crudely ignore all unreachable instruction
++ *    warnings for the entire object file.  Ideally we would just ignore them
++ *    for the function, but that would require redesigning the code quite a
++ *    bit.  And honestly that's just not worth doing: unreachable instruction
++ *    warnings are of questionable value anyway, and this is such a rare issue.
++ *
++ * 3. mov [rodata addr],%reg1
++ *    ... some instructions ...
++ *    jmpq *(%reg1,%reg2,8)
++ *
++ *    This is a fairly uncommon pattern which is new for GCC 6.  As of this
++ *    writing, there are 11 occurrences of it in the allmodconfig kernel.
++ *
++ *    TODO: Once we have DWARF CFI and smarter instruction decoding logic,
++ *    ensure the same register is used in the mov and jump instructions.
++ */
++static struct rela *find_switch_table(struct objtool_file *file,
++                                    struct symbol *func,
++                                    struct instruction *insn)
++{
++      struct rela *text_rela, *rodata_rela;
++      struct instruction *orig_insn = insn;
++
++      text_rela = find_rela_by_dest_range(insn->sec, insn->offset, insn->len);
++      if (text_rela && text_rela->sym == file->rodata->sym) {
++              /* case 1 */
++              rodata_rela = find_rela_by_dest(file->rodata,
++                                              text_rela->addend);
++              if (rodata_rela)
++                      return rodata_rela;
++
++              /* case 2 */
++              rodata_rela = find_rela_by_dest(file->rodata,
++                                              text_rela->addend + 4);
++              if (!rodata_rela)
++                      return NULL;
++              file->ignore_unreachables = true;
++              return rodata_rela;
++      }
++
++      /* case 3 */
++      func_for_each_insn_continue_reverse(file, func, insn) {
++              if (insn->type == INSN_JUMP_DYNAMIC)
++                      break;
++
++              /* allow small jumps within the range */
++              if (insn->type == INSN_JUMP_UNCONDITIONAL &&
++                  insn->jump_dest &&
++                  (insn->jump_dest->offset <= insn->offset ||
++                   insn->jump_dest->offset > orig_insn->offset))
++                  break;
++
++              /* look for a relocation which references .rodata */
++              text_rela = find_rela_by_dest_range(insn->sec, insn->offset,
++                                                  insn->len);
++              if (!text_rela || text_rela->sym != file->rodata->sym)
++                      continue;
++
++              /*
++               * Make sure the .rodata address isn't associated with a
++               * symbol.  gcc jump tables are anonymous data.
++               */
++              if (find_symbol_containing(file->rodata, text_rela->addend))
++                      continue;
++
++              return find_rela_by_dest(file->rodata, text_rela->addend);
++      }
++
++      return NULL;
++}
++
++static int add_func_switch_tables(struct objtool_file *file,
++                                struct symbol *func)
++{
++      struct instruction *insn, *prev_jump = NULL;
++      struct rela *rela, *prev_rela = NULL;
++      int ret;
++
++      func_for_each_insn(file, func, insn) {
++              if (insn->type != INSN_JUMP_DYNAMIC)
++                      continue;
++
++              rela = find_switch_table(file, func, insn);
++              if (!rela)
++                      continue;
++
++              /*
++               * We found a switch table, but we don't know yet how big it
++               * is.  Don't add it until we reach the end of the function or
++               * the beginning of another switch table in the same function.
++               */
++              if (prev_jump) {
++                      ret = add_switch_table(file, func, prev_jump, prev_rela,
++                                             rela);
++                      if (ret)
++                              return ret;
++              }
++
++              prev_jump = insn;
++              prev_rela = rela;
++      }
++
++      if (prev_jump) {
++              ret = add_switch_table(file, func, prev_jump, prev_rela, NULL);
++              if (ret)
++                      return ret;
++      }
++
++      return 0;
++}
++
++/*
++ * For some switch statements, gcc generates a jump table in the .rodata
++ * section which contains a list of addresses within the function to jump to.
++ * This finds these jump tables and adds them to the insn->alts lists.
++ */
++static int add_switch_table_alts(struct objtool_file *file)
++{
++      struct section *sec;
++      struct symbol *func;
++      int ret;
++
++      if (!file->rodata || !file->rodata->rela)
++              return 0;
++
++      list_for_each_entry(sec, &file->elf->sections, list) {
++              list_for_each_entry(func, &sec->symbol_list, list) {
++                      if (func->type != STT_FUNC)
++                              continue;
++
++                      ret = add_func_switch_tables(file, func);
++                      if (ret)
++                              return ret;
++              }
++      }
++
++      return 0;
++}
++
++static int decode_sections(struct objtool_file *file)
++{
++      int ret;
++
++      ret = decode_instructions(file);
++      if (ret)
++              return ret;
++
++      ret = add_dead_ends(file);
++      if (ret)
++              return ret;
++
++      add_ignores(file);
++
++      ret = add_nospec_ignores(file);
++      if (ret)
++              return ret;
++
++      ret = add_jump_destinations(file);
++      if (ret)
++              return ret;
++
++      ret = add_call_destinations(file);
++      if (ret)
++              return ret;
++
++      ret = add_special_section_alts(file);
++      if (ret)
++              return ret;
++
++      ret = add_switch_table_alts(file);
++      if (ret)
++              return ret;
++
++      return 0;
++}
++
++static bool is_fentry_call(struct instruction *insn)
++{
++      if (insn->type == INSN_CALL &&
++          insn->call_dest->type == STT_NOTYPE &&
++          !strcmp(insn->call_dest->name, "__fentry__"))
++              return true;
++
++      return false;
++}
++
++static bool has_modified_stack_frame(struct instruction *insn)
++{
++      return (insn->state & STATE_FP_SAVED) ||
++             (insn->state & STATE_FP_SETUP);
++}
++
++static bool has_valid_stack_frame(struct instruction *insn)
++{
++      return (insn->state & STATE_FP_SAVED) &&
++             (insn->state & STATE_FP_SETUP);
++}
++
++static unsigned int frame_state(unsigned long state)
++{
++      return (state & (STATE_FP_SAVED | STATE_FP_SETUP));
++}
++
++/*
++ * Follow the branch starting at the given instruction, and recursively follow
++ * any other branches (jumps).  Meanwhile, track the frame pointer state at
++ * each instruction and validate all the rules described in
++ * tools/objtool/Documentation/stack-validation.txt.
++ */
++static int validate_branch(struct objtool_file *file,
++                         struct instruction *first, unsigned char first_state)
++{
++      struct alternative *alt;
++      struct instruction *insn;
++      struct section *sec;
++      struct symbol *func = NULL;
++      unsigned char state;
++      int ret;
++
++      insn = first;
++      sec = insn->sec;
++      state = first_state;
++
++      if (insn->alt_group && list_empty(&insn->alts)) {
++              WARN_FUNC("don't know how to handle branch to middle of alternative instruction group",
++                        sec, insn->offset);
++              return 1;
++      }
++
++      while (1) {
++              if (file->c_file && insn->func) {
++                      if (func && func != insn->func) {
++                              WARN("%s() falls through to next function %s()",
++                                   func->name, insn->func->name);
++                              return 1;
++                      }
++
++                      func = insn->func;
++              }
++
++              if (insn->visited) {
++                      if (frame_state(insn->state) != frame_state(state)) {
++                              WARN_FUNC("frame pointer state mismatch",
++                                        sec, insn->offset);
++                              return 1;
++                      }
++
++                      return 0;
++              }
++
++              insn->visited = true;
++              insn->state = state;
++
++              list_for_each_entry(alt, &insn->alts, list) {
++                      ret = validate_branch(file, alt->insn, state);
++                      if (ret)
++                              return 1;
++              }
++
++              switch (insn->type) {
++
++              case INSN_FP_SAVE:
++                      if (!nofp) {
++                              if (state & STATE_FP_SAVED) {
++                                      WARN_FUNC("duplicate frame pointer save",
++                                                sec, insn->offset);
++                                      return 1;
++                              }
++                              state |= STATE_FP_SAVED;
++                      }
++                      break;
++
++              case INSN_FP_SETUP:
++                      if (!nofp) {
++                              if (state & STATE_FP_SETUP) {
++                                      WARN_FUNC("duplicate frame pointer setup",
++                                                sec, insn->offset);
++                                      return 1;
++                              }
++                              state |= STATE_FP_SETUP;
++                      }
++                      break;
++
++              case INSN_FP_RESTORE:
++                      if (!nofp) {
++                              if (has_valid_stack_frame(insn))
++                                      state &= ~STATE_FP_SETUP;
++
++                              state &= ~STATE_FP_SAVED;
++                      }
++                      break;
++
++              case INSN_RETURN:
++                      if (!nofp && has_modified_stack_frame(insn)) {
++                              WARN_FUNC("return without frame pointer restore",
++                                        sec, insn->offset);
++                              return 1;
++                      }
++                      return 0;
++
++              case INSN_CALL:
++                      if (is_fentry_call(insn)) {
++                              state |= STATE_FENTRY;
++                              break;
++                      }
++
++                      ret = dead_end_function(file, insn->call_dest);
++                      if (ret == 1)
++                              return 0;
++                      if (ret == -1)
++                              return 1;
++
++                      /* fallthrough */
++              case INSN_CALL_DYNAMIC:
++                      if (!nofp && !has_valid_stack_frame(insn)) {
++                              WARN_FUNC("call without frame pointer save/setup",
++                                        sec, insn->offset);
++                              return 1;
++                      }
++                      break;
++
++              case INSN_JUMP_CONDITIONAL:
++              case INSN_JUMP_UNCONDITIONAL:
++                      if (insn->jump_dest) {
++                              ret = validate_branch(file, insn->jump_dest,
++                                                    state);
++                              if (ret)
++                                      return 1;
++                      } else if (has_modified_stack_frame(insn)) {
++                              WARN_FUNC("sibling call from callable instruction with changed frame pointer",
++                                        sec, insn->offset);
++                              return 1;
++                      } /* else it's a sibling call */
++
++                      if (insn->type == INSN_JUMP_UNCONDITIONAL)
++                              return 0;
++
++                      break;
++
++              case INSN_JUMP_DYNAMIC:
++                      if (list_empty(&insn->alts) &&
++                          has_modified_stack_frame(insn)) {
++                              WARN_FUNC("sibling call from callable instruction with changed frame pointer",
++                                        sec, insn->offset);
++                              return 1;
++                      }
++
++                      return 0;
++
++              default:
++                      break;
++              }
++
++              if (insn->dead_end)
++                      return 0;
++
++              insn = next_insn_same_sec(file, insn);
++              if (!insn) {
++                      WARN("%s: unexpected end of section", sec->name);
++                      return 1;
++              }
++      }
++
++      return 0;
++}
++
++static bool is_kasan_insn(struct instruction *insn)
++{
++      return (insn->type == INSN_CALL &&
++              !strcmp(insn->call_dest->name, "__asan_handle_no_return"));
++}
++
++static bool is_ubsan_insn(struct instruction *insn)
++{
++      return (insn->type == INSN_CALL &&
++              !strcmp(insn->call_dest->name,
++                      "__ubsan_handle_builtin_unreachable"));
++}
++
++static bool ignore_unreachable_insn(struct symbol *func,
++                                  struct instruction *insn)
++{
++      int i;
++
++      if (insn->type == INSN_NOP)
++              return true;
++
++      /*
++       * Check if this (or a subsequent) instruction is related to
++       * CONFIG_UBSAN or CONFIG_KASAN.
++       *
++       * End the search at 5 instructions to avoid going into the weeds.
++       */
++      for (i = 0; i < 5; i++) {
++
++              if (is_kasan_insn(insn) || is_ubsan_insn(insn))
++                      return true;
++
++              if (insn->type == INSN_JUMP_UNCONDITIONAL && insn->jump_dest) {
++                      insn = insn->jump_dest;
++                      continue;
++              }
++
++              if (insn->offset + insn->len >= func->offset + func->len)
++                      break;
++              insn = list_next_entry(insn, list);
++      }
++
++      return false;
++}
++
++static int validate_functions(struct objtool_file *file)
++{
++      struct section *sec;
++      struct symbol *func;
++      struct instruction *insn;
++      int ret, warnings = 0;
++
++      list_for_each_entry(sec, &file->elf->sections, list) {
++              list_for_each_entry(func, &sec->symbol_list, list) {
++                      if (func->type != STT_FUNC)
++                              continue;
++
++                      insn = find_insn(file, sec, func->offset);
++                      if (!insn)
++                              continue;
++
++                      ret = validate_branch(file, insn, 0);
++                      warnings += ret;
++              }
++      }
++
++      list_for_each_entry(sec, &file->elf->sections, list) {
++              list_for_each_entry(func, &sec->symbol_list, list) {
++                      if (func->type != STT_FUNC)
++                              continue;
++
++                      func_for_each_insn(file, func, insn) {
++                              if (insn->visited)
++                                      continue;
++
++                              insn->visited = true;
++
++                              if (file->ignore_unreachables || warnings ||
++                                  ignore_unreachable_insn(func, insn))
++                                      continue;
++
++                              /*
++                               * gcov produces a lot of unreachable
++                               * instructions.  If we get an unreachable
++                               * warning and the file has gcov enabled, just
++                               * ignore it, and all other such warnings for
++                               * the file.
++                               */
++                              if (!file->ignore_unreachables &&
++                                  gcov_enabled(file)) {
++                                      file->ignore_unreachables = true;
++                                      continue;
++                              }
++
++                              WARN_FUNC("function has unreachable instruction", insn->sec, insn->offset);
++                              warnings++;
++                      }
++              }
++      }
++
++      return warnings;
++}
++
++static int validate_uncallable_instructions(struct objtool_file *file)
++{
++      struct instruction *insn;
++      int warnings = 0;
++
++      for_each_insn(file, insn) {
++              if (!insn->visited && insn->type == INSN_RETURN) {
++
++                      /*
++                       * Don't warn about call instructions in unvisited
++                       * retpoline alternatives.
++                       */
++                      if (!strcmp(insn->sec->name, ".altinstr_replacement"))
++                              continue;
++
++                      WARN_FUNC("return instruction outside of a callable function",
++                                insn->sec, insn->offset);
++                      warnings++;
++              }
++      }
++
++      return warnings;
++}
++
++static void cleanup(struct objtool_file *file)
++{
++      struct instruction *insn, *tmpinsn;
++      struct alternative *alt, *tmpalt;
++
++      list_for_each_entry_safe(insn, tmpinsn, &file->insn_list, list) {
++              list_for_each_entry_safe(alt, tmpalt, &insn->alts, list) {
++                      list_del(&alt->list);
++                      free(alt);
++              }
++              list_del(&insn->list);
++              hash_del(&insn->hash);
++              free(insn);
++      }
++      elf_close(file->elf);
++}
++
++int check(const char *_objname, bool _nofp)
++{
++      struct objtool_file file;
++      int ret, warnings = 0;
++
++      objname = _objname;
++      nofp = _nofp;
++
++      file.elf = elf_open(objname);
++      if (!file.elf) {
++              fprintf(stderr, "error reading elf file %s\n", objname);
++              return 1;
++      }
++
++      INIT_LIST_HEAD(&file.insn_list);
++      hash_init(file.insn_hash);
++      file.whitelist = find_section_by_name(file.elf, ".discard.func_stack_frame_non_standard");
++      file.rodata = find_section_by_name(file.elf, ".rodata");
++      file.ignore_unreachables = false;
++      file.c_file = find_section_by_name(file.elf, ".comment");
++
++      ret = decode_sections(&file);
++      if (ret < 0)
++              goto out;
++      warnings += ret;
++
++      ret = validate_functions(&file);
++      if (ret < 0)
++              goto out;
++      warnings += ret;
++
++      ret = validate_uncallable_instructions(&file);
++      if (ret < 0)
++              goto out;
++      warnings += ret;
++
++out:
++      cleanup(&file);
++
++      /* ignore warnings for now until we get all the code cleaned up */
++      if (ret || warnings)
++              return 0;
++      return 0;
++}
+--- /dev/null
++++ b/tools/objtool/check.h
+@@ -0,0 +1,51 @@
++/*
++ * Copyright (C) 2017 Josh Poimboeuf <jpoimboe@redhat.com>
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation; either version 2
++ * of the License, or (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, see <http://www.gnu.org/licenses/>.
++ */
++
++#ifndef _CHECK_H
++#define _CHECK_H
++
++#include <stdbool.h>
++#include "elf.h"
++#include "arch.h"
++#include <linux/hashtable.h>
++
++struct instruction {
++      struct list_head list;
++      struct hlist_node hash;
++      struct section *sec;
++      unsigned long offset;
++      unsigned int len, state;
++      unsigned char type;
++      unsigned long immediate;
++      bool alt_group, visited, dead_end, ignore_alts;
++      struct symbol *call_dest;
++      struct instruction *jump_dest;
++      struct list_head alts;
++      struct symbol *func;
++};
++
++struct objtool_file {
++      struct elf *elf;
++      struct list_head insn_list;
++      DECLARE_HASHTABLE(insn_hash, 16);
++      struct section *rodata, *whitelist;
++      bool ignore_unreachables, c_file;
++};
++
++int check(const char *objname, bool nofp);
++
++#endif /* _CHECK_H */
index ca01d23541da87df6214ceab36fda5eb175edb1f..116ec09a9356479a2ba83cc222bdfe5e8e161463 100644 (file)
@@ -1,3 +1,5 @@
 arm64-lse-add-early-clobbers-to-some-input-output-asm-operands.patch
 powerpc-64s-clear-pcr-on-boot.patch
 usb-serial-cp210x-use-tcflag_t-to-fix-incompatible-pointer-type.patch
+objtool-improve-detection-of-bug-and-other-dead-ends.patch
+objtool-move-checking-code-to-check.c.patch