From: Eduard Zingerman <eddyz87@gmail.com>
To: Andrii Nakryiko <andrii.nakryiko@gmail.com>
Cc: bpf@vger.kernel.org, ast@kernel.org, andrii@kernel.org,
daniel@iogearbox.net, martin.lau@linux.dev, kernel-team@fb.com,
yonghong.song@linux.dev, sunhao.th@gmail.com
Subject: Re: [PATCH bpf-next v2 1/3] bpf: track find_equal_scalars history on per-instruction level
Date: Tue, 09 Jul 2024 18:21:18 -0700 [thread overview]
Message-ID: <e1551a1e473d0497275f74a005e47841f058cf7b.camel@gmail.com> (raw)
In-Reply-To: <CAEf4Bzbq8Lg+n1K=V0RjgKh7+PFU5rrwFPP2s0Z+g_nLbUpcPA@mail.gmail.com>
On Tue, 2024-07-09 at 17:34 -0700, Andrii Nakryiko wrote:
> On Fri, Jul 5, 2024 at 1:59 PM Eduard Zingerman <eddyz87@gmail.com> wrote:
> >
> > Use bpf_verifier_state->jmp_history to track which registers were
> > updated by find_equal_scalars() when conditional jump was verified.
> > Use recorded information in backtrack_insn() to propagate precision.
> >
> > E.g. for the following program:
> >
> > while verifying instructions
> > r1 = r0 |
> > if r1 < 8 goto ... | push r0,r1 as equal_scalars in jmp_history
> > if r0 > 16 goto ... | push r0,r1 as equal_scalars in jmp_history
>
> linked_scalars? especially now that Alexei added offsets between
> linked registers
Missed this, will update.
>
> > r2 = r10 |
> > r2 += r0 v mark_chain_precision(r0)
> >
> > while doing mark_chain_precision(r0)
> > r1 = r0 ^
> > if r1 < 8 goto ... | mark r0,r1 as precise
> > if r0 > 16 goto ... | mark r0,r1 as precise
> > r2 = r10 |
> > r2 += r0 | mark r0 precise
>
> let's reverse the order here so it's linear in how the algorithm
> actually works (backwards)?
I thought the arrow would be enough. Ok, can reverse.
> > Technically, achieve this as follows:
> > - Use 10 bits to identify each register that gains range because of
> > find_equal_scalars():
>
> should this be renamed to find_linked_scalars() nowadays?
That would be sync_linked_regs() if we use naming that you suggest.
Will update.
[...]
> > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> > index e25ad5fb9115..ec493360607e 100644
> > --- a/kernel/bpf/verifier.c
> > +++ b/kernel/bpf/verifier.c
> > @@ -3335,9 +3335,87 @@ static bool is_jmp_point(struct bpf_verifier_env *env, int insn_idx)
> > return env->insn_aux_data[insn_idx].jmp_point;
> > }
> >
> > +#define ES_FRAMENO_BITS 3
> > +#define ES_SPI_BITS 6
> > +#define ES_ENTRY_BITS (ES_SPI_BITS + ES_FRAMENO_BITS + 1)
> > +#define ES_SIZE_BITS 4
> > +#define ES_FRAMENO_MASK ((1ul << ES_FRAMENO_BITS) - 1)
> > +#define ES_SPI_MASK ((1ul << ES_SPI_BITS) - 1)
> > +#define ES_SIZE_MASK ((1ul << ES_SIZE_BITS) - 1)
>
> ull for 32-bit arches?
Ok
>
> > +#define ES_SPI_OFF ES_FRAMENO_BITS
> > +#define ES_IS_REG_OFF (ES_SPI_BITS + ES_FRAMENO_BITS)
>
> ES makes no sense now, no? LR or LINKREG or something along those lines?
>
> > +#define LINKED_REGS_MAX 6
> > +
> > +struct reg_or_spill {
>
> reg_or_spill -> linked_reg ?
Ok
>
> > + u8 frameno:3;
> > + union {
> > + u8 spi:6;
> > + u8 regno:6;
> > + };
> > + bool is_reg:1;
> > +};
>
> Do we need these bitfields for unpacked representation? It's going to
> use 2 bytes for this struct anyways. If you just use u8 for everything
> you end up with 3 bytes. Bitfields are a bit slower because the
> compiler will need to do more bit manipulations, so is it really worth
> it?
Ok, will remove bitfields.
[...]
> > @@ -3844,6 +3974,7 @@ static int backtrack_insn(struct bpf_verifier_env *env, int idx, int subseq_idx,
> > */
> > bt_set_reg(bt, dreg);
> > bt_set_reg(bt, sreg);
> > + } else if (BPF_SRC(insn->code) == BPF_K) {
> > /* else dreg <cond> K
>
> drop "else" from the comment then? I like this change.
This is actually a leftover from v1. I can drop "else" from the
comment or drop this hunk as it is not necessary for the series.
> > * Only dreg still needs precision before
> > * this insn, so for the K-based conditional
> > @@ -3862,6 +3993,10 @@ static int backtrack_insn(struct bpf_verifier_env *env, int idx, int subseq_idx,
> > /* to be analyzed */
> > return -ENOTSUPP;
> > }
> > + /* Propagate precision marks to linked registers, to account for
> > + * registers marked as precise in this function.
> > + */
> > + bt_sync_linked_regs(bt, hist);
>
> Radical Andrii is fine with this, though I wonder if there is some
> place outside of backtrack_insn() where the first
> bt_sync_linked_regs() could be called just once?
The problem here is that:
- in theory linked_regs could be present for any instruction, thus
sync() is needed after get_jmp_hist_entry call in
__mark_chain_precision();
- backtrack_insn() might both remove and add some registers in bt,
hence, to correctly handle bt_empty() call in __mark_chain_precision
the sync() is also needed after backtrack_insn().
So, current placement is the simplest I could come up with.
> But regardless, this is only mildly expensive when we do have linked
> registers, so unlikely to have any noticeable performance effect.
Yes, that was my thinking as well.
[...]
> > @@ -15154,14 +15289,66 @@ static bool try_match_pkt_pointers(const struct bpf_insn *insn,
> > return true;
> > }
> >
> > -static void find_equal_scalars(struct bpf_verifier_state *vstate,
> > - struct bpf_reg_state *known_reg)
> > +static void __find_equal_scalars(struct linked_regs *reg_set, struct bpf_reg_state *reg,
> > + u32 id, u32 frameno, u32 spi_or_reg, bool is_reg)
>
> we should abandon "equal scalars" terminology, they don't have to be
> equal, they are just linked together (potentially with a fixed
> difference between them)
>
> how about "collect_linked_regs"?
Sounds good.
[...]
> > @@ -15312,6 +15500,21 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env,
> > return 0;
> > }
> >
> > + /* Push scalar registers sharing same ID to jump history,
> > + * do this before creating 'other_branch', so that both
> > + * 'this_branch' and 'other_branch' share this history
> > + * if parent state is created.
> > + */
> > + if (BPF_SRC(insn->code) == BPF_X && src_reg->type == SCALAR_VALUE && src_reg->id)
> > + find_equal_scalars(this_branch, src_reg->id, &linked_regs);
> > + if (dst_reg->type == SCALAR_VALUE && dst_reg->id)
> > + find_equal_scalars(this_branch, dst_reg->id, &linked_regs);
> > + if (linked_regs.cnt > 1) {
>
> if we have just one, should it be even marked as linked?
Sorry, I don't understand. Do you suggest to add an additional check
in find_equal_scalars/collect_linked_regs and reset it if 'cnt' equals 1?
[...]
>
> > + err = push_jmp_history(env, this_branch, 0, linked_regs_pack(&linked_regs));
> > + if (err)
> > + return err;
> > + }
> > +
> > other_branch = push_stack(env, *insn_idx + insn->off + 1, *insn_idx,
> > false);
> > if (!other_branch)
> > @@ -15336,13 +15539,13 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env,
> > if (BPF_SRC(insn->code) == BPF_X &&
> > src_reg->type == SCALAR_VALUE && src_reg->id &&
> > !WARN_ON_ONCE(src_reg->id != other_branch_regs[insn->src_reg].id)) {
> > - find_equal_scalars(this_branch, src_reg);
> > - find_equal_scalars(other_branch, &other_branch_regs[insn->src_reg]);
> > + copy_known_reg(this_branch, src_reg, &linked_regs);
> > + copy_known_reg(other_branch, &other_branch_regs[insn->src_reg], &linked_regs);
>
> I liked the "sync" terminology you used for bt, so why not call this
> "sync_linked_regs" ?
I kept the current name for the function.
Suggested name makes sense, though.
[...]
next prev parent reply other threads:[~2024-07-10 1:21 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-07-05 20:58 [PATCH bpf-next v2 0/3] bpf: track find_equal_scalars history on per-instruction level Eduard Zingerman
2024-07-05 20:58 ` [PATCH bpf-next v2 1/3] " Eduard Zingerman
2024-07-10 0:34 ` Andrii Nakryiko
2024-07-10 1:21 ` Eduard Zingerman [this message]
2024-07-10 5:28 ` Andrii Nakryiko
2024-07-10 6:36 ` Eduard Zingerman
2024-07-10 15:21 ` Andrii Nakryiko
2024-07-05 20:58 ` [PATCH bpf-next v2 2/3] bpf: remove mark_precise_scalar_ids() Eduard Zingerman
2024-07-05 20:58 ` [PATCH bpf-next v2 3/3] selftests/bpf: tests for per-insn find_equal_scalars() precision tracking Eduard Zingerman
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=e1551a1e473d0497275f74a005e47841f058cf7b.camel@gmail.com \
--to=eddyz87@gmail.com \
--cc=andrii.nakryiko@gmail.com \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=kernel-team@fb.com \
--cc=martin.lau@linux.dev \
--cc=sunhao.th@gmail.com \
--cc=yonghong.song@linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox