aboutsummaryrefslogtreecommitdiffstats
path: root/common/recipes-kernel/linux/linux-yocto-4.9.21/0087-objtool-Move-checking-code-to-check.c.patch
diff options
context:
space:
mode:
Diffstat (limited to 'common/recipes-kernel/linux/linux-yocto-4.9.21/0087-objtool-Move-checking-code-to-check.c.patch')
-rw-r--r--common/recipes-kernel/linux/linux-yocto-4.9.21/0087-objtool-Move-checking-code-to-check.c.patch2802
1 files changed, 2802 insertions, 0 deletions
diff --git a/common/recipes-kernel/linux/linux-yocto-4.9.21/0087-objtool-Move-checking-code-to-check.c.patch b/common/recipes-kernel/linux/linux-yocto-4.9.21/0087-objtool-Move-checking-code-to-check.c.patch
new file mode 100644
index 00000000..076eb364
--- /dev/null
+++ b/common/recipes-kernel/linux/linux-yocto-4.9.21/0087-objtool-Move-checking-code-to-check.c.patch
@@ -0,0 +1,2802 @@
+From 1c6b7026213ec74f811957627c80513e75f6fb96 Mon Sep 17 00:00:00 2001
+From: Josh Poimboeuf <jpoimboe@redhat.com>
+Date: Wed, 28 Jun 2017 10:11:05 -0500
+Subject: [PATCH 87/93] objtool: Move checking code to check.c
+
+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>
+[backported by hand to 4.9, this was a pain... - gregkh]
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ tools/objtool/Build | 1 +
+ tools/objtool/builtin-check.c | 1337 +----------------------------------------
+ tools/objtool/check.c | 1327 ++++++++++++++++++++++++++++++++++++++++
+ tools/objtool/check.h | 51 ++
+ 4 files changed, 1392 insertions(+), 1324 deletions(-)
+ create mode 100644 tools/objtool/check.c
+ create mode 100644 tools/objtool/check.h
+
+diff --git a/tools/objtool/Build b/tools/objtool/Build
+index d6cdece..6f2e198 100644
+--- 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
+diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c
+index ad9eda9..365c34e 100644
+--- 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,1343 +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;
+-
+- 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;
+-
+- text_rela = find_rela_by_dest_range(insn->sec, insn->offset,
+- insn->len);
+- if (text_rela && text_rela->sym == file->rodata->sym)
+- 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;
++#include "check.h"
+
+- ret = decode_instructions(file);
+- if (ret)
+- return ret;
++bool nofp;
+
+- 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);
+ }
+diff --git a/tools/objtool/check.c b/tools/objtool/check.c
+new file mode 100644
+index 0000000..b7a0af5
+--- /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;
++}
+diff --git a/tools/objtool/check.h b/tools/objtool/check.h
+new file mode 100644
+index 0000000..aca248a
+--- /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 */
+--
+2.7.4
+