diff options
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.patch | 2802 |
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 + |