I've observed GCC transform: f() { if (!static_branch_unlikely()) return; static_assert(); A; } g() { f(); } Into: f() { static_assert(); A; } g() { if (static_branch_unlikely()) f(); } Which results in the assertion landing at f+0. The transformation is valid and useful; it avoids a pointless CALL+RET sequence, so we'll have to teach objtool how to deal with this. Do this by marking all CALL destinations with static_call when called from a static_block and non_static_call when called outside a static_block. This allows us to identify functions called exclusively from a static_block and start them with a static_block. Limit to static functions and do not apply recursive. Signed-off-by: Peter Zijlstra (Intel) --- tools/objtool/check.c | 84 +++++++++++++++++++++++++++++++++++++------------- tools/objtool/elf.h | 1 2 files changed, 64 insertions(+), 21 deletions(-) --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -1207,36 +1207,78 @@ static int assert_static_jumps(struct ob return 0; } +static bool __grow_static_block(struct objtool_file *file, + struct instruction *insn) +{ + /* if a !static jump can come in here, terminate */ + if (insn->branch_target && !insn->static_jump_dest) + return false; + + switch (insn->type) { + case INSN_JUMP_UNCONDITIONAL: + /* mark this instruction, terminate this section */ + insn->static_jump_dest = true; + return false; + + /* these disturb unconditional code flow, terminate */ + case INSN_JUMP_CONDITIONAL: + case INSN_JUMP_DYNAMIC: + case INSN_RETURN: + case INSN_BUG: + return false; + + /* these return right back and don't disturb the code flow */ + case INSN_CALL: + case INSN_CALL_DYNAMIC: + break; + } + + /* mark this insn, and continue the section */ + insn->static_jump_dest = true; + return true; +} + static int grow_static_blocks(struct objtool_file *file) { - struct instruction *insn; bool static_block = false; + struct symbol *func, *tmp; + struct instruction *insn; + struct section *sec; for_each_insn(file, insn) { - if (!static_block && !insn->static_jump_dest) - continue; + if (static_block || insn->static_jump_dest) + static_block = __grow_static_block(file, insn); - if (insn->static_jump_dest) { - static_block = true; - continue; + if (insn->type == INSN_CALL) { + func = insn->call_dest; + if (!func) + continue; + + if (static_block) + func->static_call = true; + else + func->non_static_call = true; } + } - if (insn->branch_target) { - static_block = false; - continue; - } else switch (insn->type) { - case INSN_JUMP_CONDITIONAL: - case INSN_JUMP_UNCONDITIONAL: - case INSN_JUMP_DYNAMIC: - case INSN_CALL: - case INSN_CALL_DYNAMIC: - case INSN_RETURN: - case INSN_BUG: - static_block = false; - continue; + for_each_sec(file, sec) { + list_for_each_entry_safe(func, tmp, &sec->symbol_list, list) { + if (func->bind != STB_LOCAL) + continue; + + if (!func->static_call) + continue; + + if (func->non_static_call) + continue; + + /* static && !non_static -- only static callers */ + + func_for_each_insn(file, func, insn) { + if (!__grow_static_block(file, insn)) + break; + } } - - insn->static_jump_dest = static_block; } return 0; --- a/tools/objtool/elf.h +++ b/tools/objtool/elf.h @@ -61,6 +61,7 @@ struct symbol { unsigned char bind, type; unsigned long offset; unsigned int len; + bool static_call, non_static_call; }; struct rela {