BPF List
 help / color / mirror / Atom feed
* [PATCH bpf-next v3 00/13] bpf: Replace bpf_patch_insn_data() with a list of deferred patches
@ 2026-11-02  1:29 Alexei Starovoitov
  2026-11-02  1:29 ` [PATCH bpf-next v3 01/13] bpf: Add a list of deferred instruction patches Alexei Starovoitov
                   ` (14 more replies)
  0 siblings, 15 replies; 18+ messages in thread
From: Alexei Starovoitov @ 2026-11-02  1:29 UTC (permalink / raw)
  To: bpf; +Cc: daniel, andrii, eddyz87, memxor, odys.yuan

From: Alexei Starovoitov <ast@kernel.org>

After verification the verifier rewrites the prog in several passes. A pass
calls bpf_patch_insn_data() for each insn that it replaces, and
verifier_remove_insns() for each range that it removes. Each call moves all
insns behind that place and walks all insns to adjust the jumps. It also
walks each table that names insns: aux data, subprogs, line info and more.
Thus a pass that changes M of the N insns of a prog does O(N * M) work.
With bpf_jit_harden=2 pyperf600 from the selftests loads in 3.509 s, and in
0.223 s with this series.

The idea
========

A pass queues its changes in a patch list, in the order of the insns. It
commits them one time, at its end. No insn moves before the commit, thus
the pass uses old indexes only. The commit calculates two maps from old
index to new index. Then it builds the new image, and all that names an
insn goes through one of the maps. The commit costs O(N + inserted insns).

  old image   patch list        new_off[]   new_tgt[]   new image
  0: a                           0           0           0: a
  1: b        patch, 3 insns     1           1           1: b1
                                                         2: b2
                                                         3: b3
  2: c                           4           4           4: c
  3: d        removal            5           5
  4: e                           5           5           5: e
  5: f        insert 1 insn      6           7           6: i1
                                                         7: f

  map         what goes through it
  new_off[]   calls, subprogs, function pointers, line info
  new_tgt[]   jumps, insn arrays, poke descriptors

The maps differ only where insns are inserted in front of an insn. For
example, the setup of the may_goto count goes in front of a subprog. A call
of the subprog runs the setup, and a jump to its first insn does not.

The interface
=============

  bpf_patch_list_add()     queue a patch: new insns replace insn i
  bpf_patch_list_insert()  queue new insns that go in front of insn i
  bpf_patch_list_del()     queue the removal of insn i
  bpf_patch_list_commit()  build the new image, move all tables, install it
  bpf_patch_list_apply()   static in core.c: the commit for classic BPF

During a commit one part of bpf_verifier_env (env) describes the old image,
and a different part describes the new image. Thus the order of the steps
is a rule:

  1. all that can fail   Build the maps and the new image, allocate the new
                         aux data and the prog. An error changes nothing.
  2. removed insns       Tell the offload device, free what their aux data
                         points to. All of env describes the old image.
  3. the tables          A new entry depends only on the old entry and on
                         the maps. Function pointers also need the old
                         subprogs, thus they move before the subprogs.
                         No helper that reads the insns, prog->len or a
                         table from env or the prog.
  4. the new image       Write the insns and prog->len last.

The patches
===========

  pass                                  patch  what it needs (patch)
  ------------------------------------  -----  ----------------------------
  bpf_do_misc_fixups(), main loop       3      list (1), commit (2)
  bpf_opt_remove_dead_code()            5      removal (4)
  bpf_opt_remove_nops()                 5      removal (4)
  bpf_opt_subreg_zext_lo32_rnd_hi32()   5
  bpf_convert_ctx_accesses()            8      second patch (6), insert (7)
  bpf_do_misc_fixups(), may_goto count  9      insert (7)
  add_hidden_subprog()                  9
  bpf_optimize_bpf_loop()               9
  bpf_jit_blind_constants()             10     commit without env (10)

Read patches 1, 2, 4 and 7 first: they are the design. Patch 11 removes the
old code. Patches 12-13 are selftests.

The results
===========

Load times in an x86_64 VM with 8 vCPUs, bpf-next without and with the
series. Wall time of 'veristat -q <obj>', mean of 30 runs, and of 10 runs
with BPF_F_TEST_RND_HI32 or bpf_jit_harden=2. BPF_PROG_LOAD of a prog with
131072 'goto +0', mean of 3 runs.

                                   base       patched
  pyperf180                       0.148 s    0.085 s
  pyperf600                       0.430 s    0.220 s
  strobemeta                      0.149 s    0.148 s
  test_verif_scale2               0.121 s    0.122 s
  pyperf180, BPF_F_TEST_RND_HI32  0.610 s    0.091 s
  pyperf600, BPF_F_TEST_RND_HI32  1.830 s    0.223 s
  pyperf180, bpf_jit_harden=2     1.216 s    0.085 s
  pyperf600, bpf_jit_harden=2     3.509 s    0.223 s
  131072 'goto +0'               33.288 s    0.245 s

With bpf_jit_harden=2 the prog of
"ld_dw: xor semi-random 64 bit imms, test 5" of test_verifier does not load
in 900 s on the base. With the series the test takes 1.5 s.

What users can see, apart from the load time:
 - Patches 3, 5, 8, 9 and 10: a jump that patches push out of range fails
   the load with -ERANGE, not -ENOMEM. The verifier log names the jump, not
   the patched insn. For patch 10 that is true for a prog with subprogs. A
   prog of one function gets -ENOTSUPP or goes to the interpreter, as
   before.
 - Patch 3: a backward may_goto that patches push beyond -32768 loads.
 - Patch 5: bpf_opt_remove_nops() also removes a 'goto' or a may_goto that
   jumps over 'goto +0' only.
 - Patches 8 and 9: a jump back to an insn keeps its offset when insns are
   inserted in front of that insn. On the base an offset near -32768 fails
   the load.

The series does not change the code of progs in other ways. In selftests,
scx and Cilium only these nops differ in xlated insns, line info and func
info. That is also true with BPF_F_TEST_RND_HI32 and with bpf_jit_harden=2.

Eduard pointed out the cost in pyperf180 [1]. The split between core.c and
the verifier follows Jiong's RFC [2]. Andrii suggested the map there.

I picked up Qiliang's series to speed things up: patches 1-12 are his, with
my changes as listed below.

v2 -> v3:
- Rebase: patches 4 and 6 of v2 touched the arena_scalar rewrite, which
  is gone
- Squash neighbours: the 22 patches of v2 are patches 1-12, patch 13 is new
- Fix a NULL function pointer in .rodata: the commit looked up a function
  by its old offset after prog->len was the new length (patch 4)
- Give the commit the order above: one function per table, the image
  last, the line info map built with the image (patches 1, 2, 4)
- Give all instructions of a patch the orig_idx of the patched one: the
  range message said "insn 0" for the jump of may_goto (patch 2)
- Patches 4 and 5 of v2 are one patch (3): in between a backward may_goto
  at -32768 didn't load
- Walk from the end in bpf_opt_remove_nops(): the look-back was
  quadratic and couldn't be killed (patch 5)
- Commit the prologue before bpf_convert_ctx_accesses() rewrites
  instructions in place: a rewrite of the first one was lost (patch 8)
- Make bpf_patch_list_apply() static (patch 10)
- Comments of may_goto_expand(), bpf_patch_list_commit() and the error
  path of bpf_jit_blind_constants() (CI bot)
- Write may_goto as a raw instruction in the tests, so that the assembler
  doesn't have to know it (Sashiko on 22/22)
- Tests of v2 (patch 12): check the final offset of the gotol, let gotox
  return another value for each wrong landing, run the may_goto tests on
  arm64 too, run serially because of bpf_jit_harden
- Test callx after the program shrinks (patch 13)
- Test the tables of the commit that had no test, the new nop rule and
  the blinding of a classic filter (patch 13)
- Rewrite the commit logs and the cover letter, say what users can see

v1 -> v2:
- Convert every pass and remove the old patching code (Alexei)
- Build the new image in core.c, so that blinding without a verifier env
  uses the list too
- Let the list remove instructions, insert in front of one and patch one
  twice
- Pick JEQ or gotol for a backward may_goto from its offset grown by the
  patches in between, v1 picked JEQ when that offset was -32768 (CI bot)
- Test a backward may_goto expanded to gotol
- Test a jump that the patches push out of its 16-bit range, and a gotox
  to a patched insn

v2: https://lore.kernel.org/r/20261030-bpf-verifier-patch-batch-v2-0-55cd43439f7c@gmail.com
v1: https://lore.kernel.org/r/20261001-bpf-verifier-patch-batch-v1-0-a12df8a09160@gmail.com

[1] https://lore.kernel.org/bpf/a714ee96d0ad96bbc9d51037616e5c4e2790a8ec.camel@gmail.com/
[2] https://lore.kernel.org/bpf/1562275611-31790-1-git-send-email-jiong.wang@netronome.com/

Signed-off-by: Alexei Starovoitov <ast@kernel.org>

Alexei Starovoitov (1):
  selftests/bpf: Test the tables that follow patched and removed
    instructions

Qiliang Yuan (12):
  bpf: Add a list of deferred instruction patches
  bpf: Remap the verifier state through the patch list
  bpf: Queue the rewrites of bpf_do_misc_fixups()
  bpf: Let the patch list remove instructions
  bpf: Move the bpf_opt_*() passes to the patch list
  bpf: Allow patching an instruction twice through the patch list
  bpf: Teach the patch list to insert in front of an instruction
  bpf: Move bpf_convert_ctx_accesses() to the patch list
  bpf: Move the remaining verifier rewrites to the patch list
  bpf: Blind constants through the patch list
  bpf: Remove the old instruction patching code
  selftests/bpf: Test jumps around patched instructions

 include/linux/bpf.h                           |    4 +-
 include/linux/bpf_verifier.h                  |   88 +-
 include/linux/filter.h                        |   16 -
 kernel/bpf/bpf_insn_array.c                   |   36 +-
 kernel/bpf/core.c                             |  605 +++++---
 kernel/bpf/fixups.c                           | 1306 ++++++++---------
 kernel/bpf/verifier.c                         |   46 +-
 .../selftests/bpf/prog_tests/patch_list.c     |  256 ++++
 .../selftests/bpf/prog_tests/verifier.c       |    3 +
 .../testing/selftests/bpf/progs/patch_list.c  |  136 ++
 .../bpf/progs/verifier_callx_rodata.c         |  100 ++
 .../selftests/bpf/progs/verifier_patch_list.c |  426 ++++++
 12 files changed, 2040 insertions(+), 982 deletions(-)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/patch_list.c
 create mode 100644 tools/testing/selftests/bpf/progs/patch_list.c
 create mode 100644 tools/testing/selftests/bpf/progs/verifier_patch_list.c


base-commit: a30c772627b2ef4bc715332b1b04938a914556cd
-- 
2.55.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

end of thread, other threads:[~2026-11-02  6:40 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-02  1:29 [PATCH bpf-next v3 00/13] bpf: Replace bpf_patch_insn_data() with a list of deferred patches Alexei Starovoitov
2026-11-02  1:29 ` [PATCH bpf-next v3 01/13] bpf: Add a list of deferred instruction patches Alexei Starovoitov
2026-11-02  1:29 ` [PATCH bpf-next v3 02/13] bpf: Remap the verifier state through the patch list Alexei Starovoitov
2026-11-02  1:29 ` [PATCH bpf-next v3 03/13] bpf: Queue the rewrites of bpf_do_misc_fixups() Alexei Starovoitov
2026-11-02  1:29 ` [PATCH bpf-next v3 04/13] bpf: Let the patch list remove instructions Alexei Starovoitov
2026-11-02  1:29 ` [PATCH bpf-next v3 05/13] bpf: Move the bpf_opt_*() passes to the patch list Alexei Starovoitov
2026-11-02  1:29 ` [PATCH bpf-next v3 06/13] bpf: Allow patching an instruction twice through " Alexei Starovoitov
2026-11-02  1:29 ` [PATCH bpf-next v3 07/13] bpf: Teach the patch list to insert in front of an instruction Alexei Starovoitov
2026-11-02  1:29 ` [PATCH bpf-next v3 08/13] bpf: Move bpf_convert_ctx_accesses() to the patch list Alexei Starovoitov
2026-11-02  1:29 ` [PATCH bpf-next v3 09/13] bpf: Move the remaining verifier rewrites " Alexei Starovoitov
2026-11-02  1:29 ` [PATCH bpf-next v3 10/13] bpf: Blind constants through " Alexei Starovoitov
2026-11-02  1:29 ` [PATCH bpf-next v3 11/13] bpf: Remove the old instruction patching code Alexei Starovoitov
2026-11-02  1:29 ` [PATCH bpf-next v3 12/13] selftests/bpf: Test jumps around patched instructions Alexei Starovoitov
2026-11-02  1:29 ` [PATCH bpf-next v3 13/13] selftests/bpf: Test the tables that follow patched and removed instructions Alexei Starovoitov
2026-11-02  6:06 ` [PATCH bpf-next v3 00/13] bpf: Replace bpf_patch_insn_data() with a list of deferred patches Alexei Starovoitov
2026-11-02  6:30   ` Qiliang Yuan
2026-11-02  6:40     ` Alexei Starovoitov
2026-11-02  6:40 ` patchwork-bot+netdevbpf

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox