|
|
|
@@ -18,6 +18,8 @@
|
|
|
|
|
|
|
|
|
|
#define FAKE_JUMP_OFFSET -1
|
|
|
|
|
|
|
|
|
|
#define C_JUMP_TABLE_SECTION ".rodata..c_jump_table"
|
|
|
|
|
|
|
|
|
|
struct alternative {
|
|
|
|
|
struct list_head list;
|
|
|
|
|
struct instruction *insn;
|
|
|
|
@@ -95,6 +97,20 @@ static struct instruction *next_insn_same_func(struct objtool_file *file,
|
|
|
|
|
for (insn = next_insn_same_sec(file, insn); insn; \
|
|
|
|
|
insn = next_insn_same_sec(file, insn))
|
|
|
|
|
|
|
|
|
|
static bool is_sibling_call(struct instruction *insn)
|
|
|
|
|
{
|
|
|
|
|
/* An indirect jump is either a sibling call or a jump to a table. */
|
|
|
|
|
if (insn->type == INSN_JUMP_DYNAMIC)
|
|
|
|
|
return list_empty(&insn->alts);
|
|
|
|
|
|
|
|
|
|
if (insn->type != INSN_JUMP_CONDITIONAL &&
|
|
|
|
|
insn->type != INSN_JUMP_UNCONDITIONAL)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
/* add_jump_destinations() sets insn->call_dest for sibling calls. */
|
|
|
|
|
return !!insn->call_dest;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* This checks to see if the given function is a "noreturn" function.
|
|
|
|
|
*
|
|
|
|
@@ -103,14 +119,9 @@ static struct instruction *next_insn_same_func(struct objtool_file *file,
|
|
|
|
|
*
|
|
|
|
|
* For local functions, we have to detect them manually by simply looking for
|
|
|
|
|
* the lack of a return instruction.
|
|
|
|
|
*
|
|
|
|
|
* Returns:
|
|
|
|
|
* -1: error
|
|
|
|
|
* 0: no dead end
|
|
|
|
|
* 1: dead end
|
|
|
|
|
*/
|
|
|
|
|
static int __dead_end_function(struct objtool_file *file, struct symbol *func,
|
|
|
|
|
int recursion)
|
|
|
|
|
static bool __dead_end_function(struct objtool_file *file, struct symbol *func,
|
|
|
|
|
int recursion)
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
struct instruction *insn;
|
|
|
|
@@ -136,30 +147,33 @@ static int __dead_end_function(struct objtool_file *file, struct symbol *func,
|
|
|
|
|
"rewind_stack_do_exit",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (!func)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (func->bind == STB_WEAK)
|
|
|
|
|
return 0;
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (func->bind == STB_GLOBAL)
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(global_noreturns); i++)
|
|
|
|
|
if (!strcmp(func->name, global_noreturns[i]))
|
|
|
|
|
return 1;
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
if (!func->len)
|
|
|
|
|
return 0;
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
insn = find_insn(file, func->sec, func->offset);
|
|
|
|
|
if (!insn->func)
|
|
|
|
|
return 0;
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
func_for_each_insn_all(file, func, insn) {
|
|
|
|
|
empty = false;
|
|
|
|
|
|
|
|
|
|
if (insn->type == INSN_RETURN)
|
|
|
|
|
return 0;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (empty)
|
|
|
|
|
return 0;
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* A function can have a sibling call instead of a return. In that
|
|
|
|
@@ -167,40 +181,31 @@ static int __dead_end_function(struct objtool_file *file, struct symbol *func,
|
|
|
|
|
* of the sibling call returns.
|
|
|
|
|
*/
|
|
|
|
|
func_for_each_insn_all(file, func, insn) {
|
|
|
|
|
if (insn->type == INSN_JUMP_UNCONDITIONAL) {
|
|
|
|
|
if (is_sibling_call(insn)) {
|
|
|
|
|
struct instruction *dest = insn->jump_dest;
|
|
|
|
|
|
|
|
|
|
if (!dest)
|
|
|
|
|
/* sibling call to another file */
|
|
|
|
|
return 0;
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (dest->func && dest->func->pfunc != insn->func->pfunc) {
|
|
|
|
|
|
|
|
|
|
/* local sibling call */
|
|
|
|
|
if (recursion == 5) {
|
|
|
|
|
/*
|
|
|
|
|
* 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,
|
|
|
|
|
recursion + 1);
|
|
|
|
|
/* local sibling call */
|
|
|
|
|
if (recursion == 5) {
|
|
|
|
|
/*
|
|
|
|
|
* Infinite recursion: two functions have
|
|
|
|
|
* sibling calls to each other. This is a very
|
|
|
|
|
* rare case. It means they aren't dead ends.
|
|
|
|
|
*/
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (insn->type == INSN_JUMP_DYNAMIC && list_empty(&insn->alts))
|
|
|
|
|
/* sibling call */
|
|
|
|
|
return 0;
|
|
|
|
|
return __dead_end_function(file, dest->func, recursion+1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int dead_end_function(struct objtool_file *file, struct symbol *func)
|
|
|
|
|
static bool dead_end_function(struct objtool_file *file, struct symbol *func)
|
|
|
|
|
{
|
|
|
|
|
return __dead_end_function(file, func, 0);
|
|
|
|
|
}
|
|
|
|
@@ -262,19 +267,12 @@ static int decode_instructions(struct objtool_file *file)
|
|
|
|
|
if (ret)
|
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
|
|
if (!insn->type || insn->type > INSN_LAST) {
|
|
|
|
|
WARN_FUNC("invalid instruction type %d",
|
|
|
|
|
insn->sec, insn->offset, insn->type);
|
|
|
|
|
ret = -1;
|
|
|
|
|
goto err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hash_add(file->insn_hash, &insn->hash, insn->offset);
|
|
|
|
|
list_add_tail(&insn->list, &file->insn_list);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
list_for_each_entry(func, &sec->symbol_list, list) {
|
|
|
|
|
if (func->type != STT_FUNC)
|
|
|
|
|
if (func->type != STT_FUNC || func->alias != func)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (!find_insn(file, sec, func->offset)) {
|
|
|
|
@@ -284,8 +282,7 @@ static int decode_instructions(struct objtool_file *file)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func_for_each_insn(file, func, insn)
|
|
|
|
|
if (!insn->func)
|
|
|
|
|
insn->func = func;
|
|
|
|
|
insn->func = func;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -488,6 +485,7 @@ static const char *uaccess_safe_builtin[] = {
|
|
|
|
|
/* misc */
|
|
|
|
|
"csum_partial_copy_generic",
|
|
|
|
|
"__memcpy_mcsafe",
|
|
|
|
|
"mcsafe_handle_tail",
|
|
|
|
|
"ftrace_likely_update", /* CONFIG_TRACE_BRANCH_PROFILING */
|
|
|
|
|
NULL
|
|
|
|
|
};
|
|
|
|
@@ -505,7 +503,7 @@ static void add_uaccess_safe(struct objtool_file *file)
|
|
|
|
|
if (!func)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
func->alias->uaccess_safe = true;
|
|
|
|
|
func->uaccess_safe = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -577,13 +575,16 @@ static int add_jump_destinations(struct objtool_file *file)
|
|
|
|
|
* Retpoline jumps are really dynamic jumps in
|
|
|
|
|
* disguise, so convert them accordingly.
|
|
|
|
|
*/
|
|
|
|
|
insn->type = INSN_JUMP_DYNAMIC;
|
|
|
|
|
if (insn->type == INSN_JUMP_UNCONDITIONAL)
|
|
|
|
|
insn->type = INSN_JUMP_DYNAMIC;
|
|
|
|
|
else
|
|
|
|
|
insn->type = INSN_JUMP_DYNAMIC_CONDITIONAL;
|
|
|
|
|
|
|
|
|
|
insn->retpoline_safe = true;
|
|
|
|
|
continue;
|
|
|
|
|
} else {
|
|
|
|
|
/* sibling call */
|
|
|
|
|
/* external sibling call */
|
|
|
|
|
insn->call_dest = rela->sym;
|
|
|
|
|
insn->jump_dest = NULL;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -623,7 +624,7 @@ static int add_jump_destinations(struct objtool_file *file)
|
|
|
|
|
* However this code can't completely replace the
|
|
|
|
|
* read_symbols() code because this doesn't detect the
|
|
|
|
|
* case where the parent function's only reference to a
|
|
|
|
|
* subfunction is through a switch table.
|
|
|
|
|
* subfunction is through a jump table.
|
|
|
|
|
*/
|
|
|
|
|
if (!strstr(insn->func->name, ".cold.") &&
|
|
|
|
|
strstr(insn->jump_dest->func->name, ".cold.")) {
|
|
|
|
@@ -633,9 +634,8 @@ static int add_jump_destinations(struct objtool_file *file)
|
|
|
|
|
} else if (insn->jump_dest->func->pfunc != insn->func->pfunc &&
|
|
|
|
|
insn->jump_dest->offset == insn->jump_dest->func->offset) {
|
|
|
|
|
|
|
|
|
|
/* sibling class */
|
|
|
|
|
/* internal sibling call */
|
|
|
|
|
insn->call_dest = insn->jump_dest->func;
|
|
|
|
|
insn->jump_dest = NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -896,20 +896,26 @@ out:
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int add_switch_table(struct objtool_file *file, struct instruction *insn,
|
|
|
|
|
struct rela *table, struct rela *next_table)
|
|
|
|
|
static int add_jump_table(struct objtool_file *file, struct instruction *insn,
|
|
|
|
|
struct rela *table)
|
|
|
|
|
{
|
|
|
|
|
struct rela *rela = table;
|
|
|
|
|
struct instruction *alt_insn;
|
|
|
|
|
struct instruction *dest_insn;
|
|
|
|
|
struct alternative *alt;
|
|
|
|
|
struct symbol *pfunc = insn->func->pfunc;
|
|
|
|
|
unsigned int prev_offset = 0;
|
|
|
|
|
|
|
|
|
|
list_for_each_entry_from(rela, &table->rela_sec->rela_list, list) {
|
|
|
|
|
if (rela == next_table)
|
|
|
|
|
/*
|
|
|
|
|
* Each @rela is a switch table relocation which points to the target
|
|
|
|
|
* instruction.
|
|
|
|
|
*/
|
|
|
|
|
list_for_each_entry_from(rela, &table->sec->rela_list, list) {
|
|
|
|
|
|
|
|
|
|
/* Check for the end of the table: */
|
|
|
|
|
if (rela != table && rela->jump_table_start)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
/* Make sure the switch table entries are consecutive: */
|
|
|
|
|
/* Make sure the table entries are consecutive: */
|
|
|
|
|
if (prev_offset && rela->offset != prev_offset + 8)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
@@ -918,12 +924,12 @@ static int add_switch_table(struct objtool_file *file, struct instruction *insn,
|
|
|
|
|
rela->addend == pfunc->offset)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
alt_insn = find_insn(file, rela->sym->sec, rela->addend);
|
|
|
|
|
if (!alt_insn)
|
|
|
|
|
dest_insn = find_insn(file, rela->sym->sec, rela->addend);
|
|
|
|
|
if (!dest_insn)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
/* Make sure the jmp dest is in the function or subfunction: */
|
|
|
|
|
if (alt_insn->func->pfunc != pfunc)
|
|
|
|
|
/* Make sure the destination is in the same function: */
|
|
|
|
|
if (!dest_insn->func || dest_insn->func->pfunc != pfunc)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
alt = malloc(sizeof(*alt));
|
|
|
|
@@ -932,7 +938,7 @@ static int add_switch_table(struct objtool_file *file, struct instruction *insn,
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
alt->insn = alt_insn;
|
|
|
|
|
alt->insn = dest_insn;
|
|
|
|
|
list_add_tail(&alt->list, &insn->alts);
|
|
|
|
|
prev_offset = rela->offset;
|
|
|
|
|
}
|
|
|
|
@@ -947,7 +953,7 @@ static int add_switch_table(struct objtool_file *file, struct instruction *insn,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* find_switch_table() - Given a dynamic jump, find the switch jump table in
|
|
|
|
|
* find_jump_table() - Given a dynamic jump, find the switch jump table in
|
|
|
|
|
* .rodata associated with it.
|
|
|
|
|
*
|
|
|
|
|
* There are 3 basic patterns:
|
|
|
|
@@ -989,13 +995,13 @@ static int add_switch_table(struct objtool_file *file, struct instruction *insn,
|
|
|
|
|
*
|
|
|
|
|
* NOTE: RETPOLINE made it harder still to decode dynamic jumps.
|
|
|
|
|
*/
|
|
|
|
|
static struct rela *find_switch_table(struct objtool_file *file,
|
|
|
|
|
static struct rela *find_jump_table(struct objtool_file *file,
|
|
|
|
|
struct symbol *func,
|
|
|
|
|
struct instruction *insn)
|
|
|
|
|
{
|
|
|
|
|
struct rela *text_rela, *rodata_rela;
|
|
|
|
|
struct rela *text_rela, *table_rela;
|
|
|
|
|
struct instruction *orig_insn = insn;
|
|
|
|
|
struct section *rodata_sec;
|
|
|
|
|
struct section *table_sec;
|
|
|
|
|
unsigned long table_offset;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
@@ -1028,42 +1034,52 @@ static struct rela *find_switch_table(struct objtool_file *file,
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
table_offset = text_rela->addend;
|
|
|
|
|
rodata_sec = text_rela->sym->sec;
|
|
|
|
|
table_sec = text_rela->sym->sec;
|
|
|
|
|
|
|
|
|
|
if (text_rela->type == R_X86_64_PC32)
|
|
|
|
|
table_offset += 4;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Make sure the .rodata address isn't associated with a
|
|
|
|
|
* symbol. gcc jump tables are anonymous data.
|
|
|
|
|
* symbol. GCC jump tables are anonymous data.
|
|
|
|
|
*
|
|
|
|
|
* Also support C jump tables which are in the same format as
|
|
|
|
|
* switch jump tables. For objtool to recognize them, they
|
|
|
|
|
* need to be placed in the C_JUMP_TABLE_SECTION section. They
|
|
|
|
|
* have symbols associated with them.
|
|
|
|
|
*/
|
|
|
|
|
if (find_symbol_containing(rodata_sec, table_offset))
|
|
|
|
|
if (find_symbol_containing(table_sec, table_offset) &&
|
|
|
|
|
strcmp(table_sec->name, C_JUMP_TABLE_SECTION))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
rodata_rela = find_rela_by_dest(rodata_sec, 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;
|
|
|
|
|
/* Each table entry has a rela associated with it. */
|
|
|
|
|
table_rela = find_rela_by_dest(table_sec, table_offset);
|
|
|
|
|
if (!table_rela)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
return 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 table_rela;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int add_func_switch_tables(struct objtool_file *file,
|
|
|
|
|
struct symbol *func)
|
|
|
|
|
/*
|
|
|
|
|
* First pass: Mark the head of each jump table so that in the next pass,
|
|
|
|
|
* we know when a given jump table ends and the next one starts.
|
|
|
|
|
*/
|
|
|
|
|
static void mark_func_jump_tables(struct objtool_file *file,
|
|
|
|
|
struct symbol *func)
|
|
|
|
|
{
|
|
|
|
|
struct instruction *insn, *last = NULL, *prev_jump = NULL;
|
|
|
|
|
struct rela *rela, *prev_rela = NULL;
|
|
|
|
|
int ret;
|
|
|
|
|
struct instruction *insn, *last = NULL;
|
|
|
|
|
struct rela *rela;
|
|
|
|
|
|
|
|
|
|
func_for_each_insn_all(file, func, insn) {
|
|
|
|
|
if (!last)
|
|
|
|
@@ -1071,7 +1087,7 @@ static int add_func_switch_tables(struct objtool_file *file,
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Store back-pointers for unconditional forward jumps such
|
|
|
|
|
* that find_switch_table() can back-track using those and
|
|
|
|
|
* that find_jump_table() can back-track using those and
|
|
|
|
|
* avoid some potentially confusing code.
|
|
|
|
|
*/
|
|
|
|
|
if (insn->type == INSN_JUMP_UNCONDITIONAL && insn->jump_dest &&
|
|
|
|
@@ -1086,27 +1102,25 @@ static int add_func_switch_tables(struct objtool_file *file,
|
|
|
|
|
if (insn->type != INSN_JUMP_DYNAMIC)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
rela = find_switch_table(file, func, insn);
|
|
|
|
|
if (!rela)
|
|
|
|
|
rela = find_jump_table(file, func, insn);
|
|
|
|
|
if (rela) {
|
|
|
|
|
rela->jump_table_start = true;
|
|
|
|
|
insn->jump_table = rela;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int add_func_jump_tables(struct objtool_file *file,
|
|
|
|
|
struct symbol *func)
|
|
|
|
|
{
|
|
|
|
|
struct instruction *insn;
|
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
|
|
func_for_each_insn_all(file, func, insn) {
|
|
|
|
|
if (!insn->jump_table)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 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, prev_jump, prev_rela, rela);
|
|
|
|
|
if (ret)
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
prev_jump = insn;
|
|
|
|
|
prev_rela = rela;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (prev_jump) {
|
|
|
|
|
ret = add_switch_table(file, prev_jump, prev_rela, NULL);
|
|
|
|
|
ret = add_jump_table(file, insn, insn->jump_table);
|
|
|
|
|
if (ret)
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
@@ -1119,7 +1133,7 @@ static int add_func_switch_tables(struct objtool_file *file,
|
|
|
|
|
* 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_jump_table_alts(struct objtool_file *file)
|
|
|
|
|
{
|
|
|
|
|
struct section *sec;
|
|
|
|
|
struct symbol *func;
|
|
|
|
@@ -1133,7 +1147,8 @@ static int add_switch_table_alts(struct objtool_file *file)
|
|
|
|
|
if (func->type != STT_FUNC)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
ret = add_func_switch_tables(file, func);
|
|
|
|
|
mark_func_jump_tables(file, func);
|
|
|
|
|
ret = add_func_jump_tables(file, func);
|
|
|
|
|
if (ret)
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
@@ -1277,13 +1292,18 @@ static void mark_rodata(struct objtool_file *file)
|
|
|
|
|
bool found = false;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* This searches for the .rodata section or multiple .rodata.func_name
|
|
|
|
|
* sections if -fdata-sections is being used. The .str.1.1 and .str.1.8
|
|
|
|
|
* rodata sections are ignored as they don't contain jump tables.
|
|
|
|
|
* Search for the following rodata sections, each of which can
|
|
|
|
|
* potentially contain jump tables:
|
|
|
|
|
*
|
|
|
|
|
* - .rodata: can contain GCC switch tables
|
|
|
|
|
* - .rodata.<func>: same, if -fdata-sections is being used
|
|
|
|
|
* - .rodata..c_jump_table: contains C annotated jump tables
|
|
|
|
|
*
|
|
|
|
|
* .rodata.str1.* sections are ignored; they don't contain jump tables.
|
|
|
|
|
*/
|
|
|
|
|
for_each_sec(file, sec) {
|
|
|
|
|
if (!strncmp(sec->name, ".rodata", 7) &&
|
|
|
|
|
!strstr(sec->name, ".str1.")) {
|
|
|
|
|
if ((!strncmp(sec->name, ".rodata", 7) && !strstr(sec->name, ".str1.")) ||
|
|
|
|
|
!strcmp(sec->name, C_JUMP_TABLE_SECTION)) {
|
|
|
|
|
sec->rodata = true;
|
|
|
|
|
found = true;
|
|
|
|
|
}
|
|
|
|
@@ -1325,7 +1345,7 @@ static int decode_sections(struct objtool_file *file)
|
|
|
|
|
if (ret)
|
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
|
|
ret = add_switch_table_alts(file);
|
|
|
|
|
ret = add_jump_table_alts(file);
|
|
|
|
|
if (ret)
|
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
|
@@ -1873,12 +1893,12 @@ static bool insn_state_match(struct instruction *insn, struct insn_state *state)
|
|
|
|
|
static inline bool func_uaccess_safe(struct symbol *func)
|
|
|
|
|
{
|
|
|
|
|
if (func)
|
|
|
|
|
return func->alias->uaccess_safe;
|
|
|
|
|
return func->uaccess_safe;
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline const char *insn_dest_name(struct instruction *insn)
|
|
|
|
|
static inline const char *call_dest_name(struct instruction *insn)
|
|
|
|
|
{
|
|
|
|
|
if (insn->call_dest)
|
|
|
|
|
return insn->call_dest->name;
|
|
|
|
@@ -1890,13 +1910,13 @@ static int validate_call(struct instruction *insn, struct insn_state *state)
|
|
|
|
|
{
|
|
|
|
|
if (state->uaccess && !func_uaccess_safe(insn->call_dest)) {
|
|
|
|
|
WARN_FUNC("call to %s() with UACCESS enabled",
|
|
|
|
|
insn->sec, insn->offset, insn_dest_name(insn));
|
|
|
|
|
insn->sec, insn->offset, call_dest_name(insn));
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (state->df) {
|
|
|
|
|
WARN_FUNC("call to %s() with DF set",
|
|
|
|
|
insn->sec, insn->offset, insn_dest_name(insn));
|
|
|
|
|
insn->sec, insn->offset, call_dest_name(insn));
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -1920,13 +1940,12 @@ static int validate_sibling_call(struct instruction *insn, struct insn_state *st
|
|
|
|
|
* each instruction and validate all the rules described in
|
|
|
|
|
* tools/objtool/Documentation/stack-validation.txt.
|
|
|
|
|
*/
|
|
|
|
|
static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|
|
|
|
struct insn_state state)
|
|
|
|
|
static int validate_branch(struct objtool_file *file, struct symbol *func,
|
|
|
|
|
struct instruction *first, struct insn_state state)
|
|
|
|
|
{
|
|
|
|
|
struct alternative *alt;
|
|
|
|
|
struct instruction *insn, *next_insn;
|
|
|
|
|
struct section *sec;
|
|
|
|
|
struct symbol *func = NULL;
|
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
|
|
insn = first;
|
|
|
|
@@ -1947,9 +1966,6 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (insn->func)
|
|
|
|
|
func = insn->func->pfunc;
|
|
|
|
|
|
|
|
|
|
if (func && insn->ignore) {
|
|
|
|
|
WARN_FUNC("BUG: why am I validating an ignored function?",
|
|
|
|
|
sec, insn->offset);
|
|
|
|
@@ -1971,7 +1987,7 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|
|
|
|
|
|
|
|
|
i = insn;
|
|
|
|
|
save_insn = NULL;
|
|
|
|
|
func_for_each_insn_continue_reverse(file, insn->func, i) {
|
|
|
|
|
func_for_each_insn_continue_reverse(file, func, i) {
|
|
|
|
|
if (i->save) {
|
|
|
|
|
save_insn = i;
|
|
|
|
|
break;
|
|
|
|
@@ -2017,7 +2033,7 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|
|
|
|
if (alt->skip_orig)
|
|
|
|
|
skip_orig = true;
|
|
|
|
|
|
|
|
|
|
ret = validate_branch(file, alt->insn, state);
|
|
|
|
|
ret = validate_branch(file, func, alt->insn, state);
|
|
|
|
|
if (ret) {
|
|
|
|
|
if (backtrace)
|
|
|
|
|
BT_FUNC("(alt)", insn);
|
|
|
|
@@ -2055,7 +2071,7 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|
|
|
|
|
|
|
|
|
if (state.bp_scratch) {
|
|
|
|
|
WARN("%s uses BP as a scratch register",
|
|
|
|
|
insn->func->name);
|
|
|
|
|
func->name);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -2067,36 +2083,28 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|
|
|
|
if (ret)
|
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
|
|
if (insn->type == INSN_CALL) {
|
|
|
|
|
if (is_fentry_call(insn))
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
ret = dead_end_function(file, insn->call_dest);
|
|
|
|
|
if (ret == 1)
|
|
|
|
|
return 0;
|
|
|
|
|
if (ret == -1)
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!no_fp && func && !has_valid_stack_frame(&state)) {
|
|
|
|
|
if (!no_fp && func && !is_fentry_call(insn) &&
|
|
|
|
|
!has_valid_stack_frame(&state)) {
|
|
|
|
|
WARN_FUNC("call without frame pointer save/setup",
|
|
|
|
|
sec, insn->offset);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dead_end_function(file, insn->call_dest))
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case INSN_JUMP_CONDITIONAL:
|
|
|
|
|
case INSN_JUMP_UNCONDITIONAL:
|
|
|
|
|
if (func && !insn->jump_dest) {
|
|
|
|
|
if (func && is_sibling_call(insn)) {
|
|
|
|
|
ret = validate_sibling_call(insn, &state);
|
|
|
|
|
if (ret)
|
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
|
|
} else if (insn->jump_dest &&
|
|
|
|
|
(!func || !insn->jump_dest->func ||
|
|
|
|
|
insn->jump_dest->func->pfunc == func)) {
|
|
|
|
|
ret = validate_branch(file, insn->jump_dest,
|
|
|
|
|
state);
|
|
|
|
|
} else if (insn->jump_dest) {
|
|
|
|
|
ret = validate_branch(file, func,
|
|
|
|
|
insn->jump_dest, state);
|
|
|
|
|
if (ret) {
|
|
|
|
|
if (backtrace)
|
|
|
|
|
BT_FUNC("(branch)", insn);
|
|
|
|
@@ -2110,13 +2118,17 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case INSN_JUMP_DYNAMIC:
|
|
|
|
|
if (func && list_empty(&insn->alts)) {
|
|
|
|
|
case INSN_JUMP_DYNAMIC_CONDITIONAL:
|
|
|
|
|
if (func && is_sibling_call(insn)) {
|
|
|
|
|
ret = validate_sibling_call(insn, &state);
|
|
|
|
|
if (ret)
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
if (insn->type == INSN_JUMP_DYNAMIC)
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case INSN_CONTEXT_SWITCH:
|
|
|
|
|
if (func && (!next_insn || !next_insn->hint)) {
|
|
|
|
@@ -2162,7 +2174,7 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case INSN_CLAC:
|
|
|
|
|
if (!state.uaccess && insn->func) {
|
|
|
|
|
if (!state.uaccess && func) {
|
|
|
|
|
WARN_FUNC("redundant UACCESS disable", sec, insn->offset);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
@@ -2183,7 +2195,7 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case INSN_CLD:
|
|
|
|
|
if (!state.df && insn->func)
|
|
|
|
|
if (!state.df && func)
|
|
|
|
|
WARN_FUNC("redundant CLD", sec, insn->offset);
|
|
|
|
|
|
|
|
|
|
state.df = false;
|
|
|
|
@@ -2222,7 +2234,7 @@ static int validate_unwind_hints(struct objtool_file *file)
|
|
|
|
|
|
|
|
|
|
for_each_insn(file, insn) {
|
|
|
|
|
if (insn->hint && !insn->visited) {
|
|
|
|
|
ret = validate_branch(file, insn, state);
|
|
|
|
|
ret = validate_branch(file, insn->func, insn, state);
|
|
|
|
|
if (ret && backtrace)
|
|
|
|
|
BT_FUNC("<=== (hint)", insn);
|
|
|
|
|
warnings += ret;
|
|
|
|
@@ -2345,16 +2357,25 @@ static int validate_functions(struct objtool_file *file)
|
|
|
|
|
|
|
|
|
|
for_each_sec(file, sec) {
|
|
|
|
|
list_for_each_entry(func, &sec->symbol_list, list) {
|
|
|
|
|
if (func->type != STT_FUNC || func->pfunc != func)
|
|
|
|
|
if (func->type != STT_FUNC)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (!func->len) {
|
|
|
|
|
WARN("%s() is missing an ELF size annotation",
|
|
|
|
|
func->name);
|
|
|
|
|
warnings++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (func->pfunc != func || func->alias != func)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
insn = find_insn(file, sec, func->offset);
|
|
|
|
|
if (!insn || insn->ignore)
|
|
|
|
|
if (!insn || insn->ignore || insn->visited)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
state.uaccess = func->alias->uaccess_safe;
|
|
|
|
|
state.uaccess = func->uaccess_safe;
|
|
|
|
|
|
|
|
|
|
ret = validate_branch(file, insn, state);
|
|
|
|
|
ret = validate_branch(file, func, insn, state);
|
|
|
|
|
if (ret && backtrace)
|
|
|
|
|
BT_FUNC("<=== (func)", insn);
|
|
|
|
|
warnings += ret;
|
|
|
|
@@ -2407,7 +2428,7 @@ int check(const char *_objname, bool orc)
|
|
|
|
|
|
|
|
|
|
objname = _objname;
|
|
|
|
|
|
|
|
|
|
file.elf = elf_open(objname, orc ? O_RDWR : O_RDONLY);
|
|
|
|
|
file.elf = elf_read(objname, orc ? O_RDWR : O_RDONLY);
|
|
|
|
|
if (!file.elf)
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
|
|