objtool: Support GCC 8's cold subfunctions
GCC 8 moves a lot of unlikely code out of line to "cold" subfunctions in .text.unlikely. Properly detect the new subfunctions and treat them as extensions of the original functions. This fixes a bunch of warnings like: kernel/cgroup/cgroup.o: warning: objtool: parse_cgroup_root_flags()+0x33: sibling call from callable instruction with modified stack frame kernel/cgroup/cgroup.o: warning: objtool: cgroup_addrm_files()+0x290: sibling call from callable instruction with modified stack frame kernel/cgroup/cgroup.o: warning: objtool: cgroup_apply_control_enable()+0x25b: sibling call from callable instruction with modified stack frame kernel/cgroup/cgroup.o: warning: objtool: rebind_subsystems()+0x325: sibling call from callable instruction with modified stack frame Reported-and-tested-by: damian <damian.tometzki@icloud.com> Reported-by: Arnd Bergmann <arnd@arndb.de> Signed-off-by: Josh Poimboeuf <jpoimboe@redhat.com> Acked-by: Peter Zijlstra (Intel) <peterz@infradead.org> Cc: David Laight <David.Laight@ACULAB.COM> Cc: Greg KH <gregkh@linuxfoundation.org> Cc: Linus Torvalds <torvalds@linux-foundation.org> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Randy Dunlap <rdunlap@infradead.org> Cc: Thomas Gleixner <tglx@linutronix.de> Link: http://lkml.kernel.org/r/0965e7fcfc5f31a276f0c7f298ff770c19b68706.1525923412.git.jpoimboe@redhat.com Signed-off-by: Ingo Molnar <mingo@kernel.org>
This commit is contained in:
parent
0afd0d9e0e
commit
13810435b9
|
@ -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,28 +196,17 @@ 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) {
|
func_for_each_insn_all(file, func, insn) {
|
||||||
if (insn->sec != func->sec ||
|
|
||||||
insn->offset >= func->offset + func->len)
|
|
||||||
break;
|
|
||||||
|
|
||||||
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 ||
|
if (dest->func && dest->func->pfunc != insn->func->pfunc) {
|
||||||
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;
|
|
||||||
|
|
||||||
|
/* local sibling call */
|
||||||
if (recursion == 5) {
|
if (recursion == 5) {
|
||||||
/*
|
/*
|
||||||
* Infinite recursion: two functions
|
* Infinite recursion: two functions
|
||||||
|
@ -199,7 +217,7 @@ static int __dead_end_function(struct objtool_file *file, struct symbol *func,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return __dead_end_function(file, dest_func,
|
return __dead_end_function(file, dest->func,
|
||||||
recursion + 1);
|
recursion + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -426,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -786,9 +804,8 @@ out:
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int add_switch_table(struct objtool_file *file, struct symbol *func,
|
static int add_switch_table(struct objtool_file *file, struct instruction *insn,
|
||||||
struct instruction *insn, struct rela *table,
|
struct rela *table, struct rela *next_table)
|
||||||
struct rela *next_table)
|
|
||||||
{
|
{
|
||||||
struct rela *rela = table;
|
struct rela *rela = table;
|
||||||
struct instruction *alt_insn;
|
struct instruction *alt_insn;
|
||||||
|
@ -798,18 +815,13 @@ static int add_switch_table(struct objtool_file *file, struct symbol *func,
|
||||||
if (rela == next_table)
|
if (rela == next_table)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (rela->sym->sec != insn->sec ||
|
alt_insn = find_insn(file, rela->sym->sec, rela->addend);
|
||||||
rela->addend <= func->offset ||
|
if (!alt_insn)
|
||||||
rela->addend >= func->offset + func->len)
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
alt_insn = find_insn(file, insn->sec, rela->addend);
|
/* Make sure the jmp dest is in the function or subfunction: */
|
||||||
if (!alt_insn) {
|
if (alt_insn->func->pfunc != insn->func->pfunc)
|
||||||
WARN("%s: can't find instruction at %s+0x%x",
|
break;
|
||||||
file->rodata->rela->name, insn->sec->name,
|
|
||||||
rela->addend);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
alt = malloc(sizeof(*alt));
|
alt = malloc(sizeof(*alt));
|
||||||
if (!alt) {
|
if (!alt) {
|
||||||
|
@ -947,7 +959,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;
|
||||||
|
|
||||||
|
@ -978,8 +990,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,
|
ret = add_switch_table(file, prev_jump, prev_rela, rela);
|
||||||
rela);
|
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -989,7 +1000,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;
|
||||||
}
|
}
|
||||||
|
@ -1753,15 +1764,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->pfunc) {
|
||||||
if (file->c_file && func && insn->func && func != insn->func) {
|
|
||||||
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 ? insn->func->pfunc : NULL;
|
||||||
func = insn->func;
|
|
||||||
|
|
||||||
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?",
|
||||||
|
@ -1782,7 +1791,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;
|
||||||
|
@ -1869,7 +1878,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)
|
||||||
|
@ -2064,7 +2073,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);
|
||||||
|
|
|
@ -79,6 +79,19 @@ struct symbol *find_symbol_by_offset(struct section *sec, unsigned long offset)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct symbol *find_symbol_by_name(struct elf *elf, const char *name)
|
||||||
|
{
|
||||||
|
struct section *sec;
|
||||||
|
struct symbol *sym;
|
||||||
|
|
||||||
|
list_for_each_entry(sec, &elf->sections, list)
|
||||||
|
list_for_each_entry(sym, &sec->symbol_list, list)
|
||||||
|
if (!strcmp(sym->name, name))
|
||||||
|
return sym;
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
struct symbol *find_symbol_containing(struct section *sec, unsigned long offset)
|
struct symbol *find_symbol_containing(struct section *sec, unsigned long offset)
|
||||||
{
|
{
|
||||||
struct symbol *sym;
|
struct symbol *sym;
|
||||||
|
@ -203,10 +216,11 @@ static int read_sections(struct elf *elf)
|
||||||
|
|
||||||
static int read_symbols(struct elf *elf)
|
static int read_symbols(struct elf *elf)
|
||||||
{
|
{
|
||||||
struct section *symtab;
|
struct section *symtab, *sec;
|
||||||
struct symbol *sym;
|
struct symbol *sym, *pfunc;
|
||||||
struct list_head *entry, *tmp;
|
struct list_head *entry, *tmp;
|
||||||
int symbols_nr, i;
|
int symbols_nr, i;
|
||||||
|
char *coldstr;
|
||||||
|
|
||||||
symtab = find_section_by_name(elf, ".symtab");
|
symtab = find_section_by_name(elf, ".symtab");
|
||||||
if (!symtab) {
|
if (!symtab) {
|
||||||
|
@ -281,6 +295,30 @@ static int read_symbols(struct elf *elf)
|
||||||
hash_add(sym->sec->symbol_hash, &sym->hash, sym->idx);
|
hash_add(sym->sec->symbol_hash, &sym->hash, sym->idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Create parent/child links for any cold subfunctions */
|
||||||
|
list_for_each_entry(sec, &elf->sections, list) {
|
||||||
|
list_for_each_entry(sym, &sec->symbol_list, list) {
|
||||||
|
if (sym->type != STT_FUNC)
|
||||||
|
continue;
|
||||||
|
sym->pfunc = sym->cfunc = sym;
|
||||||
|
coldstr = strstr(sym->name, ".cold.");
|
||||||
|
if (coldstr) {
|
||||||
|
coldstr[0] = '\0';
|
||||||
|
pfunc = find_symbol_by_name(elf, sym->name);
|
||||||
|
coldstr[0] = '.';
|
||||||
|
|
||||||
|
if (!pfunc) {
|
||||||
|
WARN("%s(): can't find parent function",
|
||||||
|
sym->name);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
sym->pfunc = pfunc;
|
||||||
|
pfunc->cfunc = sym;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
err:
|
err:
|
||||||
|
|
|
@ -61,6 +61,7 @@ struct symbol {
|
||||||
unsigned char bind, type;
|
unsigned char bind, type;
|
||||||
unsigned long offset;
|
unsigned long offset;
|
||||||
unsigned int len;
|
unsigned int len;
|
||||||
|
struct symbol *pfunc, *cfunc;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct rela {
|
struct rela {
|
||||||
|
@ -86,6 +87,7 @@ struct elf {
|
||||||
struct elf *elf_open(const char *name, int flags);
|
struct elf *elf_open(const char *name, int flags);
|
||||||
struct section *find_section_by_name(struct elf *elf, const char *name);
|
struct section *find_section_by_name(struct elf *elf, const char *name);
|
||||||
struct symbol *find_symbol_by_offset(struct section *sec, unsigned long offset);
|
struct symbol *find_symbol_by_offset(struct section *sec, unsigned long offset);
|
||||||
|
struct symbol *find_symbol_by_name(struct elf *elf, const char *name);
|
||||||
struct symbol *find_symbol_containing(struct section *sec, unsigned long offset);
|
struct symbol *find_symbol_containing(struct section *sec, unsigned long offset);
|
||||||
struct rela *find_rela_by_dest(struct section *sec, unsigned long offset);
|
struct rela *find_rela_by_dest(struct section *sec, unsigned long offset);
|
||||||
struct rela *find_rela_by_dest_range(struct section *sec, unsigned long offset,
|
struct rela *find_rela_by_dest_range(struct section *sec, unsigned long offset,
|
||||||
|
|
Loading…
Reference in New Issue