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, 0 insertions, 2802 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 deleted file mode 100644 index 076eb364..00000000 --- a/common/recipes-kernel/linux/linux-yocto-4.9.21/0087-objtool-Move-checking-code-to-check.c.patch +++ /dev/null @@ -1,2802 +0,0 @@ -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 - |