[PATCH v2 11/10] objtool: Even more complex static block checks

From: Peter Zijlstra
Date: Tue Jan 16 2018 - 14:49:32 EST


Subject: objtool: Even more complex static block checks
From: Peter Zijlstra <peterz@xxxxxxxxxxxxx>
Date: Tue Jan 16 20:17:01 CET 2018

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.

Signed-off-by: Peter Zijlstra (Intel) <peterz@xxxxxxxxxxxxx>
---
tools/objtool/check.c | 77 ++++++++++++++++++++++++++++++++++++--------------
tools/objtool/elf.h | 1
2 files changed, 57 insertions(+), 21 deletions(-)

--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -1199,36 +1199,71 @@ static int read_retpoline_hints(struct o
return 0;
}

+static void __grow_static_block(struct instruction *insn, bool *state)
+{
+ if (!*state && !insn->static_jump_dest)
+ return;
+
+ if (insn->static_jump_dest) {
+ *state = true;
+ return;
+ }
+
+ if (insn->branch_target) {
+ *state = false;
+ return;
+
+ } else switch (insn->type) {
+ case INSN_JUMP_CONDITIONAL:
+ case INSN_JUMP_UNCONDITIONAL:
+ case INSN_JUMP_DYNAMIC:
+ case INSN_CALL_DYNAMIC:
+ case INSN_RETURN:
+ case INSN_BUG:
+ *state = false;
+ return;
+ }
+
+ insn->static_jump_dest = *state;
+}
+
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;
+ __grow_static_block(insn, &static_block);

- if (insn->static_jump_dest) {
- static_block = true;
- continue;
- }
+ if (insn->type == INSN_CALL) {
+ func = insn->call_dest;

- 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;
+ if (static_block)
+ func->static_call = true;
+ else
+ func->non_static_call = true;
}
+ }
+
+ for_each_sec(file, sec) {
+ list_for_each_entry_safe(func, tmp, &sec->symbol_list, list) {
+ if (!func->static_call)
+ continue;

- insn->static_jump_dest = static_block;
+ if (func->non_static_call)
+ continue;
+
+ /* static && !non_static -- only static callers */
+
+ static_block = true;
+ func_for_each_insn(file, func, insn) {
+ __grow_static_block(insn, &static_block);
+ if (!static_block)
+ break;
+ }
+ }
}

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 {