|
@@ -100,7 +100,6 @@ static bool gcov_enabled(struct objtool_file *file)
|
|
|
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)
|
|
@@ -113,11 +112,6 @@ static bool ignore_func(struct objtool_file *file, struct symbol *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;
|
|
|
}
|
|
|
|
|
@@ -879,6 +873,99 @@ static int add_switch_table_alts(struct objtool_file *file)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+static int read_unwind_hints(struct objtool_file *file)
|
|
|
+{
|
|
|
+ struct section *sec, *relasec;
|
|
|
+ struct rela *rela;
|
|
|
+ struct unwind_hint *hint;
|
|
|
+ struct instruction *insn;
|
|
|
+ struct cfi_reg *cfa;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ sec = find_section_by_name(file->elf, ".discard.unwind_hints");
|
|
|
+ if (!sec)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ relasec = sec->rela;
|
|
|
+ if (!relasec) {
|
|
|
+ WARN("missing .rela.discard.unwind_hints section");
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (sec->len % sizeof(struct unwind_hint)) {
|
|
|
+ WARN("struct unwind_hint size mismatch");
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ file->hints = true;
|
|
|
+
|
|
|
+ for (i = 0; i < sec->len / sizeof(struct unwind_hint); i++) {
|
|
|
+ hint = (struct unwind_hint *)sec->data->d_buf + i;
|
|
|
+
|
|
|
+ rela = find_rela_by_dest(sec, i * sizeof(*hint));
|
|
|
+ if (!rela) {
|
|
|
+ WARN("can't find rela for unwind_hints[%d]", i);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ insn = find_insn(file, rela->sym->sec, rela->addend);
|
|
|
+ if (!insn) {
|
|
|
+ WARN("can't find insn for unwind_hints[%d]", i);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ cfa = &insn->state.cfa;
|
|
|
+
|
|
|
+ if (hint->type == UNWIND_HINT_TYPE_SAVE) {
|
|
|
+ insn->save = true;
|
|
|
+ continue;
|
|
|
+
|
|
|
+ } else if (hint->type == UNWIND_HINT_TYPE_RESTORE) {
|
|
|
+ insn->restore = true;
|
|
|
+ insn->hint = true;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ insn->hint = true;
|
|
|
+
|
|
|
+ switch (hint->sp_reg) {
|
|
|
+ case ORC_REG_UNDEFINED:
|
|
|
+ cfa->base = CFI_UNDEFINED;
|
|
|
+ break;
|
|
|
+ case ORC_REG_SP:
|
|
|
+ cfa->base = CFI_SP;
|
|
|
+ break;
|
|
|
+ case ORC_REG_BP:
|
|
|
+ cfa->base = CFI_BP;
|
|
|
+ break;
|
|
|
+ case ORC_REG_SP_INDIRECT:
|
|
|
+ cfa->base = CFI_SP_INDIRECT;
|
|
|
+ break;
|
|
|
+ case ORC_REG_R10:
|
|
|
+ cfa->base = CFI_R10;
|
|
|
+ break;
|
|
|
+ case ORC_REG_R13:
|
|
|
+ cfa->base = CFI_R13;
|
|
|
+ break;
|
|
|
+ case ORC_REG_DI:
|
|
|
+ cfa->base = CFI_DI;
|
|
|
+ break;
|
|
|
+ case ORC_REG_DX:
|
|
|
+ cfa->base = CFI_DX;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ WARN_FUNC("unsupported unwind_hint sp base reg %d",
|
|
|
+ insn->sec, insn->offset, hint->sp_reg);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ cfa->offset = hint->sp_offset;
|
|
|
+ insn->state.type = hint->type;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
static int decode_sections(struct objtool_file *file)
|
|
|
{
|
|
|
int ret;
|
|
@@ -909,6 +996,10 @@ static int decode_sections(struct objtool_file *file)
|
|
|
if (ret)
|
|
|
return ret;
|
|
|
|
|
|
+ ret = read_unwind_hints(file);
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
@@ -1382,7 +1473,7 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|
|
struct insn_state state)
|
|
|
{
|
|
|
struct alternative *alt;
|
|
|
- struct instruction *insn;
|
|
|
+ struct instruction *insn, *next_insn;
|
|
|
struct section *sec;
|
|
|
struct symbol *func = NULL;
|
|
|
int ret;
|
|
@@ -1397,6 +1488,8 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|
|
}
|
|
|
|
|
|
while (1) {
|
|
|
+ next_insn = next_insn_same_sec(file, insn);
|
|
|
+
|
|
|
if (file->c_file && insn->func) {
|
|
|
if (func && func != insn->func) {
|
|
|
WARN("%s() falls through to next function %s()",
|
|
@@ -1414,13 +1507,54 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|
|
}
|
|
|
|
|
|
if (insn->visited) {
|
|
|
- if (!!insn_state_match(insn, &state))
|
|
|
+ if (!insn->hint && !insn_state_match(insn, &state))
|
|
|
return 1;
|
|
|
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
- insn->state = state;
|
|
|
+ if (insn->hint) {
|
|
|
+ if (insn->restore) {
|
|
|
+ struct instruction *save_insn, *i;
|
|
|
+
|
|
|
+ i = insn;
|
|
|
+ save_insn = NULL;
|
|
|
+ func_for_each_insn_continue_reverse(file, func, i) {
|
|
|
+ if (i->save) {
|
|
|
+ save_insn = i;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!save_insn) {
|
|
|
+ WARN_FUNC("no corresponding CFI save for CFI restore",
|
|
|
+ sec, insn->offset);
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!save_insn->visited) {
|
|
|
+ /*
|
|
|
+ * Oops, no state to copy yet.
|
|
|
+ * Hopefully we can reach this
|
|
|
+ * instruction from another branch
|
|
|
+ * after the save insn has been
|
|
|
+ * visited.
|
|
|
+ */
|
|
|
+ if (insn == first)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ WARN_FUNC("objtool isn't smart enough to handle this CFI save/restore combo",
|
|
|
+ sec, insn->offset);
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ insn->state = save_insn->state;
|
|
|
+ }
|
|
|
+
|
|
|
+ state = insn->state;
|
|
|
+
|
|
|
+ } else
|
|
|
+ insn->state = state;
|
|
|
|
|
|
insn->visited = true;
|
|
|
|
|
@@ -1497,6 +1631,14 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
+ case INSN_CONTEXT_SWITCH:
|
|
|
+ if (func && (!next_insn || !next_insn->hint)) {
|
|
|
+ WARN_FUNC("unsupported instruction in callable function",
|
|
|
+ sec, insn->offset);
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+
|
|
|
case INSN_STACK:
|
|
|
if (update_insn_state(insn, &state))
|
|
|
return -1;
|
|
@@ -1510,7 +1652,7 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|
|
if (insn->dead_end)
|
|
|
return 0;
|
|
|
|
|
|
- insn = next_insn_same_sec(file, insn);
|
|
|
+ insn = next_insn;
|
|
|
if (!insn) {
|
|
|
WARN("%s: unexpected end of section", sec->name);
|
|
|
return 1;
|
|
@@ -1520,6 +1662,27 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+static int validate_unwind_hints(struct objtool_file *file)
|
|
|
+{
|
|
|
+ struct instruction *insn;
|
|
|
+ int ret, warnings = 0;
|
|
|
+ struct insn_state state;
|
|
|
+
|
|
|
+ if (!file->hints)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ clear_insn_state(&state);
|
|
|
+
|
|
|
+ for_each_insn(file, insn) {
|
|
|
+ if (insn->hint && !insn->visited) {
|
|
|
+ ret = validate_branch(file, insn, state);
|
|
|
+ warnings += ret;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return warnings;
|
|
|
+}
|
|
|
+
|
|
|
static bool is_kasan_insn(struct instruction *insn)
|
|
|
{
|
|
|
return (insn->type == INSN_CALL &&
|
|
@@ -1665,8 +1828,9 @@ int check(const char *_objname, bool _nofp, bool orc)
|
|
|
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");
|
|
|
+ file.ignore_unreachables = false;
|
|
|
+ file.hints = false;
|
|
|
|
|
|
arch_initial_func_cfi_state(&initial_func_cfi);
|
|
|
|
|
@@ -1683,6 +1847,11 @@ int check(const char *_objname, bool _nofp, bool orc)
|
|
|
goto out;
|
|
|
warnings += ret;
|
|
|
|
|
|
+ ret = validate_unwind_hints(&file);
|
|
|
+ if (ret < 0)
|
|
|
+ goto out;
|
|
|
+ warnings += ret;
|
|
|
+
|
|
|
if (!warnings) {
|
|
|
ret = validate_reachable_instructions(&file);
|
|
|
if (ret < 0)
|