|
@@ -59,6 +59,31 @@ static struct instruction *next_insn_same_sec(struct objtool_file *file,
|
|
return next;
|
|
return next;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static struct instruction *next_insn_same_func(struct objtool_file *file,
|
|
|
|
+ struct instruction *insn)
|
|
|
|
+{
|
|
|
|
+ struct instruction *next = list_next_entry(insn, list);
|
|
|
|
+ struct symbol *func = insn->func;
|
|
|
|
+
|
|
|
|
+ if (!func)
|
|
|
|
+ return NULL;
|
|
|
|
+
|
|
|
|
+ if (&next->list != &file->insn_list && next->func == func)
|
|
|
|
+ return next;
|
|
|
|
+
|
|
|
|
+ /* Check if we're already in the subfunction: */
|
|
|
|
+ if (func == func->cfunc)
|
|
|
|
+ return NULL;
|
|
|
|
+
|
|
|
|
+ /* Move to the subfunction: */
|
|
|
|
+ return find_insn(file, func->cfunc->sec, func->cfunc->offset);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+#define func_for_each_insn_all(file, func, insn) \
|
|
|
|
+ for (insn = find_insn(file, func->sec, func->offset); \
|
|
|
|
+ insn; \
|
|
|
|
+ insn = next_insn_same_func(file, insn))
|
|
|
|
+
|
|
#define func_for_each_insn(file, func, insn) \
|
|
#define func_for_each_insn(file, func, insn) \
|
|
for (insn = find_insn(file, func->sec, func->offset); \
|
|
for (insn = find_insn(file, func->sec, func->offset); \
|
|
insn && &insn->list != &file->insn_list && \
|
|
insn && &insn->list != &file->insn_list && \
|
|
@@ -149,10 +174,14 @@ static int __dead_end_function(struct objtool_file *file, struct symbol *func,
|
|
if (!strcmp(func->name, global_noreturns[i]))
|
|
if (!strcmp(func->name, global_noreturns[i]))
|
|
return 1;
|
|
return 1;
|
|
|
|
|
|
- if (!func->sec)
|
|
|
|
|
|
+ if (!func->len)
|
|
return 0;
|
|
return 0;
|
|
|
|
|
|
- func_for_each_insn(file, func, insn) {
|
|
|
|
|
|
+ insn = find_insn(file, func->sec, func->offset);
|
|
|
|
+ if (!insn->func)
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ func_for_each_insn_all(file, func, insn) {
|
|
empty = false;
|
|
empty = false;
|
|
|
|
|
|
if (insn->type == INSN_RETURN)
|
|
if (insn->type == INSN_RETURN)
|
|
@@ -167,35 +196,28 @@ static int __dead_end_function(struct objtool_file *file, struct symbol *func,
|
|
* case, the function's dead-end status depends on whether the target
|
|
* case, the function's dead-end status depends on whether the target
|
|
* of the sibling call returns.
|
|
* of the sibling call returns.
|
|
*/
|
|
*/
|
|
- func_for_each_insn(file, func, insn) {
|
|
|
|
- if (insn->sec != func->sec ||
|
|
|
|
- insn->offset >= func->offset + func->len)
|
|
|
|
- break;
|
|
|
|
-
|
|
|
|
|
|
+ func_for_each_insn_all(file, func, insn) {
|
|
if (insn->type == INSN_JUMP_UNCONDITIONAL) {
|
|
if (insn->type == INSN_JUMP_UNCONDITIONAL) {
|
|
struct instruction *dest = insn->jump_dest;
|
|
struct instruction *dest = insn->jump_dest;
|
|
- struct symbol *dest_func;
|
|
|
|
|
|
|
|
if (!dest)
|
|
if (!dest)
|
|
/* sibling call to another file */
|
|
/* sibling call to another file */
|
|
return 0;
|
|
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 (dest->func && dest->func->pfunc != insn->func->pfunc) {
|
|
|
|
|
|
|
|
+ /* local sibling call */
|
|
if (recursion == 5) {
|
|
if (recursion == 5) {
|
|
- WARN_FUNC("infinite recursion (objtool bug!)",
|
|
|
|
- dest->sec, dest->offset);
|
|
|
|
- return -1;
|
|
|
|
|
|
+ /*
|
|
|
|
+ * Infinite recursion: two functions
|
|
|
|
+ * have sibling calls to each other.
|
|
|
|
+ * This is a very rare case. It means
|
|
|
|
+ * they aren't dead ends.
|
|
|
|
+ */
|
|
|
|
+ return 0;
|
|
}
|
|
}
|
|
|
|
|
|
- return __dead_end_function(file, dest_func,
|
|
|
|
|
|
+ return __dead_end_function(file, dest->func,
|
|
recursion + 1);
|
|
recursion + 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -422,7 +444,7 @@ static void add_ignores(struct objtool_file *file)
|
|
if (!ignore_func(file, func))
|
|
if (!ignore_func(file, func))
|
|
continue;
|
|
continue;
|
|
|
|
|
|
- func_for_each_insn(file, func, insn)
|
|
|
|
|
|
+ func_for_each_insn_all(file, func, insn)
|
|
insn->ignore = true;
|
|
insn->ignore = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -782,30 +804,35 @@ out:
|
|
return ret;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
|
|
-static int add_switch_table(struct objtool_file *file, struct symbol *func,
|
|
|
|
- struct instruction *insn, struct rela *table,
|
|
|
|
- struct rela *next_table)
|
|
|
|
|
|
+static int add_switch_table(struct objtool_file *file, struct instruction *insn,
|
|
|
|
+ struct rela *table, struct rela *next_table)
|
|
{
|
|
{
|
|
struct rela *rela = table;
|
|
struct rela *rela = table;
|
|
struct instruction *alt_insn;
|
|
struct instruction *alt_insn;
|
|
struct alternative *alt;
|
|
struct alternative *alt;
|
|
|
|
+ struct symbol *pfunc = insn->func->pfunc;
|
|
|
|
+ unsigned int prev_offset = 0;
|
|
|
|
|
|
list_for_each_entry_from(rela, &file->rodata->rela->rela_list, list) {
|
|
list_for_each_entry_from(rela, &file->rodata->rela->rela_list, list) {
|
|
if (rela == next_table)
|
|
if (rela == next_table)
|
|
break;
|
|
break;
|
|
|
|
|
|
- if (rela->sym->sec != insn->sec ||
|
|
|
|
- rela->addend <= func->offset ||
|
|
|
|
- rela->addend >= func->offset + func->len)
|
|
|
|
|
|
+ /* Make sure the switch table entries are consecutive: */
|
|
|
|
+ if (prev_offset && rela->offset != prev_offset + 8)
|
|
break;
|
|
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;
|
|
|
|
- }
|
|
|
|
|
|
+ /* Detect function pointers from contiguous objects: */
|
|
|
|
+ if (rela->sym->sec == pfunc->sec &&
|
|
|
|
+ rela->addend == pfunc->offset)
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ alt_insn = find_insn(file, rela->sym->sec, rela->addend);
|
|
|
|
+ if (!alt_insn)
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ /* Make sure the jmp dest is in the function or subfunction: */
|
|
|
|
+ if (alt_insn->func->pfunc != pfunc)
|
|
|
|
+ break;
|
|
|
|
|
|
alt = malloc(sizeof(*alt));
|
|
alt = malloc(sizeof(*alt));
|
|
if (!alt) {
|
|
if (!alt) {
|
|
@@ -815,6 +842,13 @@ static int add_switch_table(struct objtool_file *file, struct symbol *func,
|
|
|
|
|
|
alt->insn = alt_insn;
|
|
alt->insn = alt_insn;
|
|
list_add_tail(&alt->list, &insn->alts);
|
|
list_add_tail(&alt->list, &insn->alts);
|
|
|
|
+ prev_offset = rela->offset;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!prev_offset) {
|
|
|
|
+ WARN_FUNC("can't find switch jump table",
|
|
|
|
+ insn->sec, insn->offset);
|
|
|
|
+ return -1;
|
|
}
|
|
}
|
|
|
|
|
|
return 0;
|
|
return 0;
|
|
@@ -869,40 +903,21 @@ static struct rela *find_switch_table(struct objtool_file *file,
|
|
{
|
|
{
|
|
struct rela *text_rela, *rodata_rela;
|
|
struct rela *text_rela, *rodata_rela;
|
|
struct instruction *orig_insn = insn;
|
|
struct instruction *orig_insn = insn;
|
|
|
|
+ unsigned long table_offset;
|
|
|
|
|
|
- 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 */
|
|
|
|
/*
|
|
/*
|
|
* Backward search using the @first_jump_src links, these help avoid
|
|
* Backward search using the @first_jump_src links, these help avoid
|
|
* much of the 'in between' code. Which avoids us getting confused by
|
|
* much of the 'in between' code. Which avoids us getting confused by
|
|
* it.
|
|
* it.
|
|
*/
|
|
*/
|
|
- for (insn = list_prev_entry(insn, list);
|
|
|
|
-
|
|
|
|
|
|
+ for (;
|
|
&insn->list != &file->insn_list &&
|
|
&insn->list != &file->insn_list &&
|
|
insn->sec == func->sec &&
|
|
insn->sec == func->sec &&
|
|
insn->offset >= func->offset;
|
|
insn->offset >= func->offset;
|
|
|
|
|
|
insn = insn->first_jump_src ?: list_prev_entry(insn, list)) {
|
|
insn = insn->first_jump_src ?: list_prev_entry(insn, list)) {
|
|
|
|
|
|
- if (insn->type == INSN_JUMP_DYNAMIC)
|
|
|
|
|
|
+ if (insn != orig_insn && insn->type == INSN_JUMP_DYNAMIC)
|
|
break;
|
|
break;
|
|
|
|
|
|
/* allow small jumps within the range */
|
|
/* allow small jumps within the range */
|
|
@@ -918,18 +933,29 @@ static struct rela *find_switch_table(struct objtool_file *file,
|
|
if (!text_rela || text_rela->sym != file->rodata->sym)
|
|
if (!text_rela || text_rela->sym != file->rodata->sym)
|
|
continue;
|
|
continue;
|
|
|
|
|
|
|
|
+ table_offset = text_rela->addend;
|
|
|
|
+ if (text_rela->type == R_X86_64_PC32)
|
|
|
|
+ table_offset += 4;
|
|
|
|
+
|
|
/*
|
|
/*
|
|
* Make sure the .rodata address isn't associated with a
|
|
* Make sure the .rodata address isn't associated with a
|
|
* symbol. gcc jump tables are anonymous data.
|
|
* symbol. gcc jump tables are anonymous data.
|
|
*/
|
|
*/
|
|
- if (find_symbol_containing(file->rodata, text_rela->addend))
|
|
|
|
|
|
+ if (find_symbol_containing(file->rodata, table_offset))
|
|
continue;
|
|
continue;
|
|
|
|
|
|
- rodata_rela = find_rela_by_dest(file->rodata, text_rela->addend);
|
|
|
|
- if (!rodata_rela)
|
|
|
|
- continue;
|
|
|
|
|
|
+ rodata_rela = find_rela_by_dest(file->rodata, table_offset);
|
|
|
|
+ if (rodata_rela) {
|
|
|
|
+ /*
|
|
|
|
+ * Use of RIP-relative switch jumps is quite rare, and
|
|
|
|
+ * indicates a rare GCC quirk/bug which can leave dead
|
|
|
|
+ * code behind.
|
|
|
|
+ */
|
|
|
|
+ if (text_rela->type == R_X86_64_PC32)
|
|
|
|
+ file->ignore_unreachables = true;
|
|
|
|
|
|
- return rodata_rela;
|
|
|
|
|
|
+ return rodata_rela;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
return NULL;
|
|
return NULL;
|
|
@@ -943,7 +969,7 @@ static int add_func_switch_tables(struct objtool_file *file,
|
|
struct rela *rela, *prev_rela = NULL;
|
|
struct rela *rela, *prev_rela = NULL;
|
|
int ret;
|
|
int ret;
|
|
|
|
|
|
- func_for_each_insn(file, func, insn) {
|
|
|
|
|
|
+ func_for_each_insn_all(file, func, insn) {
|
|
if (!last)
|
|
if (!last)
|
|
last = insn;
|
|
last = insn;
|
|
|
|
|
|
@@ -974,8 +1000,7 @@ static int add_func_switch_tables(struct objtool_file *file,
|
|
* the beginning of another switch table in the same function.
|
|
* the beginning of another switch table in the same function.
|
|
*/
|
|
*/
|
|
if (prev_jump) {
|
|
if (prev_jump) {
|
|
- ret = add_switch_table(file, func, prev_jump, prev_rela,
|
|
|
|
- rela);
|
|
|
|
|
|
+ ret = add_switch_table(file, prev_jump, prev_rela, rela);
|
|
if (ret)
|
|
if (ret)
|
|
return ret;
|
|
return ret;
|
|
}
|
|
}
|
|
@@ -985,7 +1010,7 @@ static int add_func_switch_tables(struct objtool_file *file,
|
|
}
|
|
}
|
|
|
|
|
|
if (prev_jump) {
|
|
if (prev_jump) {
|
|
- ret = add_switch_table(file, func, prev_jump, prev_rela, NULL);
|
|
|
|
|
|
+ ret = add_switch_table(file, prev_jump, prev_rela, NULL);
|
|
if (ret)
|
|
if (ret)
|
|
return ret;
|
|
return ret;
|
|
}
|
|
}
|
|
@@ -1749,15 +1774,13 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|
while (1) {
|
|
while (1) {
|
|
next_insn = next_insn_same_sec(file, insn);
|
|
next_insn = next_insn_same_sec(file, insn);
|
|
|
|
|
|
-
|
|
|
|
- if (file->c_file && func && insn->func && func != insn->func) {
|
|
|
|
|
|
+ if (file->c_file && func && insn->func && func != insn->func->pfunc) {
|
|
WARN("%s() falls through to next function %s()",
|
|
WARN("%s() falls through to next function %s()",
|
|
func->name, insn->func->name);
|
|
func->name, insn->func->name);
|
|
return 1;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
|
|
- if (insn->func)
|
|
|
|
- func = insn->func;
|
|
|
|
|
|
+ func = insn->func ? insn->func->pfunc : NULL;
|
|
|
|
|
|
if (func && insn->ignore) {
|
|
if (func && insn->ignore) {
|
|
WARN_FUNC("BUG: why am I validating an ignored function?",
|
|
WARN_FUNC("BUG: why am I validating an ignored function?",
|
|
@@ -1778,7 +1801,7 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|
|
|
|
|
i = insn;
|
|
i = insn;
|
|
save_insn = NULL;
|
|
save_insn = NULL;
|
|
- func_for_each_insn_continue_reverse(file, func, i) {
|
|
|
|
|
|
+ func_for_each_insn_continue_reverse(file, insn->func, i) {
|
|
if (i->save) {
|
|
if (i->save) {
|
|
save_insn = i;
|
|
save_insn = i;
|
|
break;
|
|
break;
|
|
@@ -1865,7 +1888,7 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|
case INSN_JUMP_UNCONDITIONAL:
|
|
case INSN_JUMP_UNCONDITIONAL:
|
|
if (insn->jump_dest &&
|
|
if (insn->jump_dest &&
|
|
(!func || !insn->jump_dest->func ||
|
|
(!func || !insn->jump_dest->func ||
|
|
- func == insn->jump_dest->func)) {
|
|
|
|
|
|
+ insn->jump_dest->func->pfunc == func)) {
|
|
ret = validate_branch(file, insn->jump_dest,
|
|
ret = validate_branch(file, insn->jump_dest,
|
|
state);
|
|
state);
|
|
if (ret)
|
|
if (ret)
|
|
@@ -2060,7 +2083,7 @@ static int validate_functions(struct objtool_file *file)
|
|
|
|
|
|
for_each_sec(file, sec) {
|
|
for_each_sec(file, sec) {
|
|
list_for_each_entry(func, &sec->symbol_list, list) {
|
|
list_for_each_entry(func, &sec->symbol_list, list) {
|
|
- if (func->type != STT_FUNC)
|
|
|
|
|
|
+ if (func->type != STT_FUNC || func->pfunc != func)
|
|
continue;
|
|
continue;
|
|
|
|
|
|
insn = find_insn(file, sec, func->offset);
|
|
insn = find_insn(file, sec, func->offset);
|