diff options
Diffstat (limited to 'tools/objtool')
-rw-r--r-- | tools/objtool/builtin-check.c | 140 |
1 files changed, 88 insertions, 52 deletions
diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c index 17fa7fc34fdf..bd09d0effef8 100644 --- a/tools/objtool/builtin-check.c +++ b/tools/objtool/builtin-check.c @@ -107,6 +107,12 @@ static struct instruction *next_insn_same_sec(struct objtool_file *file, 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)) @@ -664,65 +670,95 @@ static int add_switch_table(struct objtool_file *file, struct symbol *func, return 0; } -static int add_func_switch_tables(struct objtool_file *file, - struct symbol *func) +/* + * 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 instruction *insn, *prev_jump; - struct rela *text_rela, *rodata_rela, *prev_rela = NULL; - int ret; + struct rela *text_rela, *rodata_rela; - prev_jump = NULL; + 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; - func_for_each_insn(file, func, insn) { - if (insn->type != INSN_JUMP_DYNAMIC) - continue; + /* 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_UNCONDITIONAL || + insn->type == INSN_JUMP_DYNAMIC) + break; text_rela = find_rela_by_dest_range(insn->sec, insn->offset, insn->len); - if (!text_rela || text_rela->sym != file->rodata->sym) - continue; + if (text_rela && text_rela->sym == file->rodata->sym) + return find_rela_by_dest(file->rodata, + text_rela->addend); + } - /* common case: jmpq *[addr](,%rax,8) */ - rodata_rela = find_rela_by_dest(file->rodata, - text_rela->addend); + return NULL; +} - /* - * rare case: jmpq *[addr](%rip) - * - * This check is for 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. - * - * kbuild reports: - * - https://lkml.kernel.org/r/201603231906.LWcVUpxm%25fengguang.wu@intel.com - * - https://lkml.kernel.org/r/201603271114.K9i45biy%25fengguang.wu@intel.com - * - https://lkml.kernel.org/r/201603291058.zuJ6ben1%25fengguang.wu@intel.com - * - * gcc bug: - * - https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70604 - */ - if (!rodata_rela) { - rodata_rela = find_rela_by_dest(file->rodata, - text_rela->addend + 4); - if (rodata_rela) - file->ignore_unreachables = true; - } +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; - if (!rodata_rela) + func_for_each_insn(file, func, insn) { + if (insn->type != INSN_JUMP_DYNAMIC) + continue; + + rela = find_switch_table(file, func, insn); + if (!rela) continue; /* @@ -732,13 +768,13 @@ static int add_func_switch_tables(struct objtool_file *file, */ if (prev_jump) { ret = add_switch_table(file, func, prev_jump, prev_rela, - rodata_rela); + rela); if (ret) return ret; } prev_jump = insn; - prev_rela = rodata_rela; + prev_rela = rela; } if (prev_jump) { |