From: Alexei Starovoitov return copy_to_user(to, from, n) ? -EFAULT : 0; GCC for x86-64 never loads -14 into a register here: neg %rax sbb %eax,%eax 0 or -1 and $0xfffffff2,%eax 0 or -14 ret So the function gets no tag. Other forms of the same select: one of two errors sbb; and; sub the condition is negated sbb; not; and The table of x86-64 describes "sbb %eax,%eax" as a definition with two constants, 0 and -1. Describe "dst = src op imm" in text_insn.h as TEXT_ALU_IMM. text_flow_fold() runs before the analysis. It computes the instruction if src still holds the constants of an earlier instruction, and the code falls through from there. The result is again a definition with two constants. Five patterns of x86-64 use it: "and" and "sub" of a short immediate, in 32 and in 64 bits, and "not". functions tagged before after x86-64 GCC 12076 12256 x86-64 clang 12994 13002 arm64 clang 11837 11837 Signed-off-by: Alexei Starovoitov Acked-by: Anton Protopopov --- tools/bpf/resolve_btfids/fmodret_scan.c | 1 + tools/bpf/resolve_btfids/text_flow.c | 49 +++++++++++++++++++++++++ tools/bpf/resolve_btfids/text_flow.h | 6 +++ tools/bpf/resolve_btfids/text_insn.h | 8 ++++ tools/bpf/resolve_btfids/text_mc.cpp | 3 ++ tools/bpf/resolve_btfids/text_mc.h | 1 + tools/bpf/resolve_btfids/text_mc_x86.c | 10 +++++ 7 files changed, 78 insertions(+) diff --git a/tools/bpf/resolve_btfids/fmodret_scan.c b/tools/bpf/resolve_btfids/fmodret_scan.c index 6df10be92610..5b5eb8cb414b 100644 --- a/tools/bpf/resolve_btfids/fmodret_scan.c +++ b/tools/bpf/resolve_btfids/fmodret_scan.c @@ -160,6 +160,7 @@ int scan_func(struct scan *s, const struct image_func *f, const int64_t **values return err; if (!has_entry_nop(flow, f->addr)) return 0; + text_flow_fold(&s->flow); text_flow_solve(&s->flow); s->nr_values = 0; diff --git a/tools/bpf/resolve_btfids/text_flow.c b/tools/bpf/resolve_btfids/text_flow.c index 2d58ffb2e939..3fe2c75eb2fc 100644 --- a/tools/bpf/resolve_btfids/text_flow.c +++ b/tools/bpf/resolve_btfids/text_flow.c @@ -16,6 +16,54 @@ static u32 next_insn(const struct text_flow *flow, u32 i) i + 1 : TEXT_NO_INSN; } +static u64 alu(const struct text_insn *insn, u64 v) +{ + switch (insn->op) { + case TEXT_AND: + v &= insn->imm; + break; + case TEXT_XOR: + v ^= insn->imm; + break; + case TEXT_SUB: + v -= insn->imm; + break; + } + return insn->is32 ? (u32)v : v; +} + +void text_flow_fold(struct text_flow *flow) +{ + /* The instruction which has left a constant in each register */ + u32 def[TEXT_MAX_REGS], i, r; + + for (i = 0; i < flow->nr_insns; i++) { + struct text_insn *insn = &flow->insns[i]; + u32 defs = insn->defs; + + /* The function or a cold subfunction begins */ + if (!i || next_insn(flow, i - 1) == TEXT_NO_INSN) + for (r = 0; r < TEXT_MAX_REGS; r++) + def[r] = TEXT_NO_INSN; + if (insn->kind == TEXT_ALU_IMM && def[insn->src] != TEXT_NO_INSN) { + insn->kind = TEXT_MOV_IMM; + insn->imm2 = alu(insn, flow->insns[def[insn->src]].imm2); + insn->imm = alu(insn, flow->insns[def[insn->src]].imm); + } + if (insn->kind == TEXT_BRANCH || insn->kind == TEXT_RETURN) + defs = TEXT_ALL_REGS; + else if (insn->kind == TEXT_CALL) + defs |= flow->arch->caller_saved; + else if (insn->kind != TEXT_OTHER && insn->kind != TEXT_COND_BRANCH) + defs |= BIT(insn->dst); + for (r = 0; defs && r < TEXT_MAX_REGS; r++) + if (defs & BIT(r)) + def[r] = TEXT_NO_INSN; + if (insn->kind == TEXT_MOV_IMM) + def[insn->dst] = i; + } +} + static u32 insn_at(const struct text_flow *flow, u64 addr) { u32 lo = 0, hi = flow->nr_insns; @@ -90,6 +138,7 @@ static struct text_facts transfer(const struct text_flow *flow, u32 i) kill(&in, flow->arch->caller_saved); break; case TEXT_MOV_IMM: + case TEXT_ALU_IMM: kill(&in, BIT(insn->dst)); break; case TEXT_MOV: diff --git a/tools/bpf/resolve_btfids/text_flow.h b/tools/bpf/resolve_btfids/text_flow.h index b307c08fd163..a266419400f9 100644 --- a/tools/bpf/resolve_btfids/text_flow.h +++ b/tools/bpf/resolve_btfids/text_flow.h @@ -36,6 +36,12 @@ struct text_flow { struct text_facts *facts; }; +/* + * Fold constants: "dst = imm or imm2" and a later "dst = src op imm" in the same + * straight line of code give "dst = imm' or imm2'". Compilers select one of two + * constants without a branch in this way. + */ +void text_flow_fold(struct text_flow *flow); /* Compute the facts */ void text_flow_solve(struct text_flow *flow); /* Return the facts behind instruction I, which is not a branch */ diff --git a/tools/bpf/resolve_btfids/text_insn.h b/tools/bpf/resolve_btfids/text_insn.h index 0ead445f5442..a79a4c9a86d5 100644 --- a/tools/bpf/resolve_btfids/text_insn.h +++ b/tools/bpf/resolve_btfids/text_insn.h @@ -18,6 +18,7 @@ enum text_insn_kind { TEXT_OTHER, TEXT_MOV_IMM, /* dst = imm or imm2 */ + TEXT_ALU_IMM, /* dst = src op imm */ TEXT_MOV, /* dst = src */ TEXT_SEXT32, /* dst = (s32)src */ TEXT_SELECT, /* dst = src or src2 */ @@ -28,6 +29,12 @@ enum text_insn_kind { TEXT_RETURN, }; +enum text_alu_op { + TEXT_AND, + TEXT_XOR, + TEXT_SUB, +}; + /* The arch numbers its registers */ #define TEXT_MAX_REGS 32 #define TEXT_ALL_REGS (~0U) @@ -37,6 +44,7 @@ struct text_insn { u8 kind; u8 len; u8 dst, src, src2; + u8 op; /* of TEXT_ALU_IMM */ bool is32; /* It works on the low 32 bits and zero extends the result */ bool nop; /* It does nothing */ /* The registers which it writes, but for dst */ diff --git a/tools/bpf/resolve_btfids/text_mc.cpp b/tools/bpf/resolve_btfids/text_mc.cpp index cb57f9db9f9d..ddf9e33ee5a0 100644 --- a/tools/bpf/resolve_btfids/text_mc.cpp +++ b/tools/bpf/resolve_btfids/text_mc.cpp @@ -284,6 +284,7 @@ bool match(const MCInst &inst, const Opcode &o, const mc_pattern &pattern, text_ ti->dst = dst.nr; ti->src = src; ti->src2 = src2; + ti->op = pattern.op; ti->imm = imm; ti->imm2 = pattern.or_zero ? 0 : imm; return true; @@ -360,6 +361,7 @@ void print(raw_ostream &os, const text_insn &ti) os << "nop " << ti.nop << " "; os << "kind " << (int)ti.kind << " is32 " << ti.is32 << " dst " << (int)ti.dst << " src " << (int)ti.src << " src2 " << (int)ti.src2 + << " op " << (int)ti.op << " defs " << format_hex(ti.defs, 1) << " imm " << format_hex(ti.imm, 1) << " imm2 " << format_hex(ti.imm2, 1); } @@ -381,6 +383,7 @@ bool run_tests() ti.nop == want.nop && ti.kind == want.kind && ti.is32 == want.is32 && ti.dst == want.dst && ti.src == want.src && ti.src2 == want.src2 && + ti.op == want.op && ti.defs == want.defs && ti.imm == want.imm && ti.imm2 == want.imm2) continue; print(error() << test->code << " is " << d.MII->getName(d.inst.getOpcode()) << ", length " diff --git a/tools/bpf/resolve_btfids/text_mc.h b/tools/bpf/resolve_btfids/text_mc.h index 8eef0860aa1c..70fe54595d60 100644 --- a/tools/bpf/resolve_btfids/text_mc.h +++ b/tools/bpf/resolve_btfids/text_mc.h @@ -64,6 +64,7 @@ struct mc_pattern { u8 dst, src, src2; /* the numbers of the operands. DST is 0 unless it is given */ u8 imm; /* the operand with the immediate */ u8 shift; /* the operand which says how far the immediate is shifted left */ + u8 op; /* of TEXT_ALU_IMM */ bool invert; /* the immediate */ bool or_zero; /* TEXT_MOV_IMM: the immediate or 0 */ bool fixed; /* the immediate is VALUE, not an operand */ diff --git a/tools/bpf/resolve_btfids/text_mc_x86.c b/tools/bpf/resolve_btfids/text_mc_x86.c index d907fac001c8..2773f35bfc12 100644 --- a/tools/bpf/resolve_btfids/text_mc_x86.c +++ b/tools/bpf/resolve_btfids/text_mc_x86.c @@ -26,6 +26,11 @@ static const struct mc_pattern x86_patterns[] = { { "SBB32rr", TEXT_MOV_IMM, VALUE(-1), .or_zero = true, .predicates = { SAME_REG(1, 2) } }, { "SBB64rr", TEXT_MOV_IMM, VALUE(-1), .or_zero = true, .predicates = { SAME_REG(1, 2) } }, { "SAR32ri", TEXT_MOV_IMM, VALUE(-1), .or_zero = true, .predicates = { IMM(2, 31) } }, + { "AND32ri8", TEXT_ALU_IMM, .src = 1, .imm = 2, .op = TEXT_AND }, + { "AND64ri8", TEXT_ALU_IMM, .src = 1, .imm = 2, .op = TEXT_AND }, + { "SUB32ri8", TEXT_ALU_IMM, .src = 1, .imm = 2, .op = TEXT_SUB }, + { "SUB64ri8", TEXT_ALU_IMM, .src = 1, .imm = 2, .op = TEXT_SUB }, + { "NOT32r", TEXT_ALU_IMM, .src = 1, .op = TEXT_XOR, VALUE(-1) }, { "LOCK_PREFIX", TEXT_OTHER }, { "NOOPL", TEXT_OTHER, .nop = true }, /* A rep prefix is not a part of the MCInst */ @@ -58,6 +63,11 @@ static const struct mc_test x86_tests[] = { T("48 98", TEXT_SEXT32, SRC(RAX)), /* cltq */ T("0f 44 c3", TEXT_SELECT, .is32 = true, .src = RBX, .src2 = RAX), /* cmove %ebx, %eax */ T("48 0f 44 c3", TEXT_SELECT, .src = RBX, .src2 = RAX), /* cmove %rbx, %rax */ + T("83 e3 f2", TEXT_ALU_IMM, .is32 = true, .dst = RBX, SRC(RBX), .op = TEXT_AND, IMMS(0xfffffff2, 0xfffffff2)), /* and $-14, %ebx */ + T("48 83 e3 f2", TEXT_ALU_IMM, .dst = RBX, SRC(RBX), .op = TEXT_AND, IMMS(-14, -14)), /* and $-14, %rbx */ + T("83 eb 0e", TEXT_ALU_IMM, .is32 = true, .dst = RBX, SRC(RBX), .op = TEXT_SUB, IMMS(14, 14)), /* sub $14, %ebx */ + T("48 83 eb 0e", TEXT_ALU_IMM, .dst = RBX, SRC(RBX), .op = TEXT_SUB, IMMS(14, 14)), /* sub $14, %rbx */ + T("f7 d0", TEXT_ALU_IMM, .is32 = true, .op = TEXT_XOR, IMMS(0xffffffff, 0xffffffff)), /* not %eax */ T("f0", TEXT_OTHER), /* lock */ T("0f 1f 44 00 00", TEXT_OTHER, .nop = true), /* nopl 0x0(%rax,%rax,1) */ T("f3 48 ab", TEXT_OTHER, .defs = BIT(RCX) | BIT(RDI)), /* rep stosq */ -- 2.55.0