Commit 0f79bb89 authored by Andrii Nakryiko's avatar Andrii Nakryiko

Merge branch 'bpf-introduce-may_goto-and-cond_break'

Alexei Starovoitov says:

====================
bpf: Introduce may_goto and cond_break

From: Alexei Starovoitov <ast@kernel.org>

v5 -> v6:
- Rename BPF_JMA to BPF_JCOND
- Addressed Andrii's review comments

v4 -> v5:
- rewrote patch 1 to avoid fake may_goto_reg and use 'u32 may_goto_cnt' instead.
  This way may_goto handling is similar to bpf_loop() processing.
- fixed bug in patch 2 that RANGE_WITHIN should not use
  rold->type == NOT_INIT as a safe signal.
- patch 3 fixed negative offset computation in cond_break macro
- using bpf_arena and cond_break recompiled lib/glob.c as bpf prog
  and it works! It will be added as a selftest to arena series.

v3 -> v4:
- fix drained issue reported by John.
  may_goto insn could be implemented with sticky state (once
  reaches 0 it stays 0), but the verifier shouldn't assume that.
  It has to explore both branches.
  Arguably drained iterator state shouldn't be there at all.
  bpf_iter_css_next() is not sticky. Can be fixed, but auditing all
  iterators for stickiness. That's an orthogonal discussion.
- explained JMA name reasons in patch 1
- fixed test_progs-no_alu32 issue and added another test

v2 -> v3: Major change
- drop bpf_can_loop() kfunc and introduce may_goto instruction instead
  kfunc is a function call while may_goto doesn't consume any registers
  and LLVM can produce much better code due to less register pressure.
- instead of counting from zero to BPF_MAX_LOOPS start from it instead
  and break out of the loop when count reaches zero
- use may_goto instruction in cond_break macro
- recognize that 'exact' state comparison doesn't need to be truly exact.
  regsafe() should ignore precision and liveness marks, but range_within
  logic is safe to use while evaluating open coded iterators.
====================

Link: https://lore.kernel.org/r/20240306031929.42666-1-alexei.starovoitov@gmail.comSigned-off-by: default avatarAndrii Nakryiko <andrii@kernel.org>
parents 9a9d1d36 0c8bbf99
...@@ -449,6 +449,7 @@ struct bpf_verifier_state { ...@@ -449,6 +449,7 @@ struct bpf_verifier_state {
u32 jmp_history_cnt; u32 jmp_history_cnt;
u32 dfs_depth; u32 dfs_depth;
u32 callback_unroll_depth; u32 callback_unroll_depth;
u32 may_goto_depth;
}; };
#define bpf_get_spilled_reg(slot, frame, mask) \ #define bpf_get_spilled_reg(slot, frame, mask) \
...@@ -619,6 +620,7 @@ struct bpf_subprog_info { ...@@ -619,6 +620,7 @@ struct bpf_subprog_info {
u32 start; /* insn idx of function entry point */ u32 start; /* insn idx of function entry point */
u32 linfo_idx; /* The idx to the main_prog->aux->linfo */ u32 linfo_idx; /* The idx to the main_prog->aux->linfo */
u16 stack_depth; /* max. stack depth used by this function */ u16 stack_depth; /* max. stack depth used by this function */
u16 stack_extra;
bool has_tail_call: 1; bool has_tail_call: 1;
bool tail_call_reachable: 1; bool tail_call_reachable: 1;
bool has_ld_abs: 1; bool has_ld_abs: 1;
......
...@@ -42,6 +42,7 @@ ...@@ -42,6 +42,7 @@
#define BPF_JSGE 0x70 /* SGE is signed '>=', GE in x86 */ #define BPF_JSGE 0x70 /* SGE is signed '>=', GE in x86 */
#define BPF_JSLT 0xc0 /* SLT is signed, '<' */ #define BPF_JSLT 0xc0 /* SLT is signed, '<' */
#define BPF_JSLE 0xd0 /* SLE is signed, '<=' */ #define BPF_JSLE 0xd0 /* SLE is signed, '<=' */
#define BPF_JCOND 0xe0 /* conditional pseudo jumps: may_goto, goto_or_nop */
#define BPF_CALL 0x80 /* function call */ #define BPF_CALL 0x80 /* function call */
#define BPF_EXIT 0x90 /* function return */ #define BPF_EXIT 0x90 /* function return */
...@@ -50,6 +51,10 @@ ...@@ -50,6 +51,10 @@
#define BPF_XCHG (0xe0 | BPF_FETCH) /* atomic exchange */ #define BPF_XCHG (0xe0 | BPF_FETCH) /* atomic exchange */
#define BPF_CMPXCHG (0xf0 | BPF_FETCH) /* atomic compare-and-write */ #define BPF_CMPXCHG (0xf0 | BPF_FETCH) /* atomic compare-and-write */
enum bpf_cond_pseudo_jmp {
BPF_MAY_GOTO = 0,
};
/* Register numbers */ /* Register numbers */
enum { enum {
BPF_REG_0 = 0, BPF_REG_0 = 0,
......
...@@ -1675,6 +1675,7 @@ bool bpf_opcode_in_insntable(u8 code) ...@@ -1675,6 +1675,7 @@ bool bpf_opcode_in_insntable(u8 code)
[BPF_LD | BPF_IND | BPF_B] = true, [BPF_LD | BPF_IND | BPF_B] = true,
[BPF_LD | BPF_IND | BPF_H] = true, [BPF_LD | BPF_IND | BPF_H] = true,
[BPF_LD | BPF_IND | BPF_W] = true, [BPF_LD | BPF_IND | BPF_W] = true,
[BPF_JMP | BPF_JCOND] = true,
}; };
#undef BPF_INSN_3_TBL #undef BPF_INSN_3_TBL
#undef BPF_INSN_2_TBL #undef BPF_INSN_2_TBL
......
...@@ -322,6 +322,10 @@ void print_bpf_insn(const struct bpf_insn_cbs *cbs, ...@@ -322,6 +322,10 @@ void print_bpf_insn(const struct bpf_insn_cbs *cbs,
} else if (insn->code == (BPF_JMP | BPF_JA)) { } else if (insn->code == (BPF_JMP | BPF_JA)) {
verbose(cbs->private_data, "(%02x) goto pc%+d\n", verbose(cbs->private_data, "(%02x) goto pc%+d\n",
insn->code, insn->off); insn->code, insn->off);
} else if (insn->code == (BPF_JMP | BPF_JCOND) &&
insn->src_reg == BPF_MAY_GOTO) {
verbose(cbs->private_data, "(%02x) may_goto pc%+d\n",
insn->code, insn->off);
} else if (insn->code == (BPF_JMP32 | BPF_JA)) { } else if (insn->code == (BPF_JMP32 | BPF_JA)) {
verbose(cbs->private_data, "(%02x) gotol pc%+d\n", verbose(cbs->private_data, "(%02x) gotol pc%+d\n",
insn->code, insn->imm); insn->code, insn->imm);
......
This diff is collapsed.
...@@ -42,6 +42,7 @@ ...@@ -42,6 +42,7 @@
#define BPF_JSGE 0x70 /* SGE is signed '>=', GE in x86 */ #define BPF_JSGE 0x70 /* SGE is signed '>=', GE in x86 */
#define BPF_JSLT 0xc0 /* SLT is signed, '<' */ #define BPF_JSLT 0xc0 /* SLT is signed, '<' */
#define BPF_JSLE 0xd0 /* SLE is signed, '<=' */ #define BPF_JSLE 0xd0 /* SLE is signed, '<=' */
#define BPF_JCOND 0xe0 /* conditional pseudo jumps: may_goto, goto_or_nop */
#define BPF_CALL 0x80 /* function call */ #define BPF_CALL 0x80 /* function call */
#define BPF_EXIT 0x90 /* function return */ #define BPF_EXIT 0x90 /* function return */
...@@ -50,6 +51,10 @@ ...@@ -50,6 +51,10 @@
#define BPF_XCHG (0xe0 | BPF_FETCH) /* atomic exchange */ #define BPF_XCHG (0xe0 | BPF_FETCH) /* atomic exchange */
#define BPF_CMPXCHG (0xf0 | BPF_FETCH) /* atomic compare-and-write */ #define BPF_CMPXCHG (0xf0 | BPF_FETCH) /* atomic compare-and-write */
enum bpf_cond_pseudo_jmp {
BPF_MAY_GOTO = 0,
};
/* Register numbers */ /* Register numbers */
enum { enum {
BPF_REG_0 = 0, BPF_REG_0 = 0,
......
...@@ -3,3 +3,4 @@ ...@@ -3,3 +3,4 @@
exceptions # JIT does not support calling kfunc bpf_throw (exceptions) exceptions # JIT does not support calling kfunc bpf_throw (exceptions)
get_stack_raw_tp # user_stack corrupted user stack (no backchain userspace) get_stack_raw_tp # user_stack corrupted user stack (no backchain userspace)
stacktrace_build_id # compare_map_keys stackid_hmap vs. stackmap err -2 errno 2 (?) stacktrace_build_id # compare_map_keys stackid_hmap vs. stackmap err -2 errno 2 (?)
verifier_iterating_callbacks
...@@ -326,6 +326,18 @@ l_true: \ ...@@ -326,6 +326,18 @@ l_true: \
}) })
#endif #endif
#define cond_break \
({ __label__ l_break, l_continue; \
asm volatile goto("1:.byte 0xe5; \
.byte 0; \
.long ((%l[l_break] - 1b - 8) / 8) & 0xffff; \
.short 0" \
:::: l_break); \
goto l_continue; \
l_break: break; \
l_continue:; \
})
#ifndef bpf_nop_mov #ifndef bpf_nop_mov
#define bpf_nop_mov(var) \ #define bpf_nop_mov(var) \
asm volatile("%[reg]=%[reg]"::[reg]"r"((short)var)) asm volatile("%[reg]=%[reg]"::[reg]"r"((short)var))
......
// SPDX-License-Identifier: GPL-2.0 // SPDX-License-Identifier: GPL-2.0
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include "bpf_misc.h" #include "bpf_misc.h"
#include "bpf_experimental.h"
struct { struct {
__uint(type, BPF_MAP_TYPE_ARRAY); __uint(type, BPF_MAP_TYPE_ARRAY);
...@@ -239,4 +237,103 @@ int bpf_loop_iter_limit_nested(void *unused) ...@@ -239,4 +237,103 @@ int bpf_loop_iter_limit_nested(void *unused)
return 1000 * a + b + c; return 1000 * a + b + c;
} }
#define ARR_SZ 1000000
int zero;
char arr[ARR_SZ];
SEC("socket")
__success __retval(0xd495cdc0)
int cond_break1(const void *ctx)
{
unsigned long i;
unsigned int sum = 0;
for (i = zero; i < ARR_SZ; cond_break, i++)
sum += i;
for (i = zero; i < ARR_SZ; i++) {
barrier_var(i);
sum += i + arr[i];
cond_break;
}
return sum;
}
SEC("socket")
__success __retval(999000000)
int cond_break2(const void *ctx)
{
int i, j;
int sum = 0;
for (i = zero; i < 1000; cond_break, i++)
for (j = zero; j < 1000; j++) {
sum += i + j;
cond_break;
}
return sum;
}
static __noinline int loop(void)
{
int i, sum = 0;
for (i = zero; i <= 1000000; i++, cond_break)
sum += i;
return sum;
}
SEC("socket")
__success __retval(0x6a5a2920)
int cond_break3(const void *ctx)
{
return loop();
}
SEC("socket")
__success __retval(1)
int cond_break4(const void *ctx)
{
int cnt = zero;
for (;;) {
/* should eventually break out of the loop */
cond_break;
cnt++;
}
/* if we looped a bit, it's a success */
return cnt > 1 ? 1 : 0;
}
static __noinline int static_subprog(void)
{
int cnt = zero;
for (;;) {
cond_break;
cnt++;
}
return cnt;
}
SEC("socket")
__success __retval(1)
int cond_break5(const void *ctx)
{
int cnt1 = zero, cnt2;
for (;;) {
cond_break;
cnt1++;
}
cnt2 = static_subprog();
/* main and subprog have to loop a bit */
return cnt1 > 1 && cnt2 > 1 ? 1 : 0;
}
char _license[] SEC("license") = "GPL"; char _license[] SEC("license") = "GPL";
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment