diff options
-rw-r--r-- | tools/objtool/builtin-check.c | 145 |
1 files changed, 100 insertions, 45 deletions
diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c index cdbdd7d..cf1e48d 100644 --- a/tools/objtool/builtin-check.c +++ b/tools/objtool/builtin-check.c @@ -61,6 +61,7 @@ struct alternative { struct objtool_file { struct elf *elf; struct list_head insn_list; + struct section *rodata; }; const char *objname; @@ -599,73 +600,125 @@ out: return ret; } -/* - * 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) +static int add_switch_table(struct objtool_file *file, struct symbol *func, + struct instruction *insn, struct rela *table, + struct rela *next_table) { - struct instruction *insn, *alt_insn; - struct rela *rodata_rela, *text_rela; - struct section *rodata; - struct symbol *func; + struct rela *rela = table; + struct instruction *alt_insn; struct alternative *alt; - for_each_insn(file, insn) { + 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; +} + +static int add_func_switch_tables(struct objtool_file *file, + struct symbol *func) +{ + struct instruction *insn, *prev_jump; + struct rela *text_rela, *rodata_rela, *prev_rela; + int ret; + + prev_jump = NULL; + + func_for_each_insn(file, func, insn) { if (insn->type != INSN_JUMP_DYNAMIC) continue; text_rela = find_rela_by_dest_range(insn->sec, insn->offset, insn->len); - if (!text_rela || strcmp(text_rela->sym->name, ".rodata")) - continue; - - rodata = find_section_by_name(file->elf, ".rodata"); - if (!rodata || !rodata->rela) + if (!text_rela || text_rela->sym != file->rodata->sym) continue; /* common case: jmpq *[addr](,%rax,8) */ - rodata_rela = find_rela_by_dest(rodata, text_rela->addend); + rodata_rela = find_rela_by_dest(file->rodata, + text_rela->addend); - /* rare case: jmpq *[addr](%rip) */ + /* + * TODO: Document where this is needed, or get rid of it. + * + * rare case: jmpq *[addr](%rip) + */ if (!rodata_rela) - rodata_rela = find_rela_by_dest(rodata, + rodata_rela = find_rela_by_dest(file->rodata, text_rela->addend + 4); + if (!rodata_rela) continue; - func = find_containing_func(insn->sec, insn->offset); - if (!func) { - WARN_FUNC("can't find containing func", - insn->sec, insn->offset); - return -1; + /* + * 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, + rodata_rela); + if (ret) + return ret; } - list_for_each_entry_from(rodata_rela, &rodata->rela->rela_list, - list) { - if (rodata_rela->sym->sec != insn->sec || - rodata_rela->addend <= func->offset || - rodata_rela->addend >= func->offset + func->len) - break; + prev_jump = insn; + prev_rela = rodata_rela; + } - alt_insn = find_insn(file, insn->sec, - rodata_rela->addend); - if (!alt_insn) { - WARN("%s: can't find instruction at %s+0x%x", - rodata->rela->name, insn->sec->name, - rodata_rela->addend); - return -1; - } + if (prev_jump) { + ret = add_switch_table(file, func, prev_jump, prev_rela, NULL); + if (ret) + return ret; + } - alt = malloc(sizeof(*alt)); - if (!alt) { - WARN("malloc failed"); - return -1; - } + return 0; +} - alt->insn = alt_insn; - list_add_tail(&alt->list, &insn->alts); +/* + * 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; } } @@ -676,6 +729,8 @@ static int decode_sections(struct objtool_file *file) { int ret; + file->rodata = find_section_by_name(file->elf, ".rodata"); + ret = decode_instructions(file); if (ret) return ret; |