[PATCH 19/24] objtool: Even more complex static block checks

From: Peter Zijlstra
Date: Tue Jan 23 2018 - 10:43:45 EST


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) <peterz@xxxxxxxxxxxxx>
---
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 {