|
|
@@ -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 @@ static int add_special_section_alts(struct objtool_file *file)
|
|
|
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;
|