Skip to content

Commit b9a6299

Browse files
author
Alexei Starovoitov
committed
Merge branch 'bpf-arm64-use-bpf-prog-pack-allocator-in-bpf-jit'
Puranjay Mohan says: ==================== bpf, arm64: use BPF prog pack allocator in BPF JIT Changes in V8 => V9: V8: https://lore.kernel.org/bpf/20240221145106.105995-1-puranjay12@gmail.com/ 1. Rebased on bpf-next/master 2. Added Acked-by: Catalin Marinas <catalin.marinas@arm.com> Changes in V7 => V8: V7: https://lore.kernel.org/bpf/20240125133159.85086-1-puranjay12@gmail.com/ 1. Rebase on bpf-next/master 2. Fix __text_poke() by removing usage of 'ret' that was never set. Changes in V6 => V7: V6: https://lore.kernel.org/all/20240124164917.119997-1-puranjay12@gmail.com/ 1. Rebase on bpf-next/master. Changes in V5 => V6: V5: https://lore.kernel.org/all/20230908144320.2474-1-puranjay12@gmail.com/ 1. Implement a text poke api to reduce code repeatition. 2. Use flush_icache_range() in place of caches_clean_inval_pou() in the functions that modify code. 3. Optimize the bpf_jit_free() by not copying the all instructions on the rw image to the ro_image Changes in V4 => v5: 1. Remove the patch for making prog pack allocator portable as it will come through the RISCV tree[1]. 2. Add a new function aarch64_insn_set() to be used in bpf_arch_text_invalidate() for putting illegal instructions after a program is removed. The earlier implementation of bpf_arch_text_invalidate() was calling aarch64_insn_patch_text_nosync() in a loop and making it slow because each call invalidated the cache. Here is test_tag now: [root@ip-172-31-6-176 bpf]# time ./test_tag test_tag: OK (40945 tests) real 0m19.695s user 0m1.514s sys 0m17.841s test_tag without these patches: [root@ip-172-31-6-176 bpf]# time ./test_tag test_tag: OK (40945 tests) real 0m21.487s user 0m1.647s sys 0m19.106s test_tag in the previous version was really slow > 2 minutes. see [2] 3. Add cache invalidation in aarch64_insn_copy() so other users can call the function without worrying about the cache. Currently only bpf_arch_text_copy() is using it, but there might be more users in the future. Chanes in V3 => V4: Changes only in 3rd patch 1. Fix the I-cache maintenance: Clean the data cache and invalidate the i-Cache only *after* the instructions have been copied to the ROX region. Chanes in V2 => V3: Changes only in 3rd patch 1. Set prog = orig_prog; in the failure path of bpf_jit_binary_pack_finalize() call. 2. Add comments explaining the usage of the offsets in the exception table. Changes in v1 => v2: 1. Make the naming consistent in the 3rd patch: ro_image and image ro_header and header ro_image_ptr and image_ptr 2. Use names dst/src in place of addr/opcode in second patch. 3. Add Acked-by: Song Liu <song@kernel.org> in 1st and 2nd patch. BPF programs currently consume a page each on ARM64. For systems with many BPF programs, this adds significant pressure to instruction TLB. High iTLB pressure usually causes slow down for the whole system. Song Liu introduced the BPF prog pack allocator[3] to mitigate the above issue. It packs multiple BPF programs into a single huge page. It is currently only enabled for the x86_64 BPF JIT. This patch series enables the BPF prog pack allocator for the ARM64 BPF JIT. ==================================================== Performance Analysis of prog pack allocator on ARM64 ==================================================== To test the performance of the BPF prog pack allocator on ARM64, a stresser tool[4] was built. This tool loads 8 BPF programs on the system and triggers 5 of them in an infinite loop by doing system calls. The runner script starts 20 instances of the above which loads 8*20=160 BPF programs on the system, 5*20=100 of which are being constantly triggered. In the above environment we try to build Python-3.8.4 and try to find different iTLB metrics for the compilation done by gcc-12.2.0. The source code[5] is configured with the following command: ./configure --enable-optimizations --with-ensurepip=install Then the runner script is executed with the following command: ./run.sh "perf stat -e ITLB_WALK,L1I_TLB,INST_RETIRED,iTLB-load-misses -a make -j32" This builds Python while 160 BPF programs are loaded and 100 are being constantly triggered and measures iTLB related metrics. The output of the above command is discussed below before and after enabling the BPF prog pack allocator. The tests were run on qemu-system-aarch64 with 32 cpus, 4G memory, -machine virt, -cpu host, and -enable-kvm. Results ------- Before enabling prog pack allocator: ------------------------------------ Performance counter stats for 'system wide': 333278635 ITLB_WALK 6762692976558 L1I_TLB 25359571423901 INST_RETIRED 15824054789 iTLB-load-misses 189.029769053 seconds time elapsed After enabling prog pack allocator: ----------------------------------- Performance counter stats for 'system wide': 190333544 ITLB_WALK 6712712386528 L1I_TLB 25278233304411 INST_RETIRED 5716757866 iTLB-load-misses 185.392650561 seconds time elapsed Improvements in metrics ----------------------- Compilation time ---> 1.92% faster iTLB-load-misses/Sec (Less is better) ---> 63.16% decrease ITLB_WALK/1000 INST_RETIRED (Less is better) ---> 42.71% decrease ITLB_Walk/L1I_TLB (Less is better) ---> 42.47% decrease [1] https://git.kernel.org/pub/scm/linux/kernel/git/riscv/linux.git/commit/?h=for-next&id=20e490adea279d49d57b800475938f5b67926d98 [2] https://lore.kernel.org/all/CANk7y0gcP3dF2mESLp5JN1+9iDfgtiWRFGqLkCgZD6wby1kQOw@mail.gmail.com/ [3] https://lore.kernel.org/bpf/20220204185742.271030-1-song@kernel.org/ [4] https://github.com/puranjaymohan/BPF-Allocator-Bench [5] https://www.python.org/ftp/python/3.8.4/Python-3.8.4.tgz ==================== Link: https://lore.kernel.org/r/20240228141824.119877-1-puranjay12@gmail.com Signed-off-by: Alexei Starovoitov <ast@kernel.org>
2 parents e59997d + 1dad391 commit b9a6299

File tree

3 files changed

+192
-24
lines changed

3 files changed

+192
-24
lines changed

arch/arm64/include/asm/patching.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ int aarch64_insn_read(void *addr, u32 *insnp);
88
int aarch64_insn_write(void *addr, u32 insn);
99

1010
int aarch64_insn_write_literal_u64(void *addr, u64 val);
11+
void *aarch64_insn_set(void *dst, u32 insn, size_t len);
12+
void *aarch64_insn_copy(void *dst, void *src, size_t len);
1113

1214
int aarch64_insn_patch_text_nosync(void *addr, u32 insn);
1315
int aarch64_insn_patch_text(void *addrs[], u32 insns[], int cnt);

arch/arm64/kernel/patching.c

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,81 @@ noinstr int aarch64_insn_write_literal_u64(void *addr, u64 val)
105105
return ret;
106106
}
107107

108+
typedef void text_poke_f(void *dst, void *src, size_t patched, size_t len);
109+
110+
static void *__text_poke(text_poke_f func, void *addr, void *src, size_t len)
111+
{
112+
unsigned long flags;
113+
size_t patched = 0;
114+
size_t size;
115+
void *waddr;
116+
void *ptr;
117+
118+
raw_spin_lock_irqsave(&patch_lock, flags);
119+
120+
while (patched < len) {
121+
ptr = addr + patched;
122+
size = min_t(size_t, PAGE_SIZE - offset_in_page(ptr),
123+
len - patched);
124+
125+
waddr = patch_map(ptr, FIX_TEXT_POKE0);
126+
func(waddr, src, patched, size);
127+
patch_unmap(FIX_TEXT_POKE0);
128+
129+
patched += size;
130+
}
131+
raw_spin_unlock_irqrestore(&patch_lock, flags);
132+
133+
flush_icache_range((uintptr_t)addr, (uintptr_t)addr + len);
134+
135+
return addr;
136+
}
137+
138+
static void text_poke_memcpy(void *dst, void *src, size_t patched, size_t len)
139+
{
140+
copy_to_kernel_nofault(dst, src + patched, len);
141+
}
142+
143+
static void text_poke_memset(void *dst, void *src, size_t patched, size_t len)
144+
{
145+
u32 c = *(u32 *)src;
146+
147+
memset32(dst, c, len / 4);
148+
}
149+
150+
/**
151+
* aarch64_insn_copy - Copy instructions into (an unused part of) RX memory
152+
* @dst: address to modify
153+
* @src: source of the copy
154+
* @len: length to copy
155+
*
156+
* Useful for JITs to dump new code blocks into unused regions of RX memory.
157+
*/
158+
noinstr void *aarch64_insn_copy(void *dst, void *src, size_t len)
159+
{
160+
/* A64 instructions must be word aligned */
161+
if ((uintptr_t)dst & 0x3)
162+
return NULL;
163+
164+
return __text_poke(text_poke_memcpy, dst, src, len);
165+
}
166+
167+
/**
168+
* aarch64_insn_set - memset for RX memory regions.
169+
* @dst: address to modify
170+
* @insn: value to set
171+
* @len: length of memory region.
172+
*
173+
* Useful for JITs to fill regions of RX memory with illegal instructions.
174+
*/
175+
noinstr void *aarch64_insn_set(void *dst, u32 insn, size_t len)
176+
{
177+
if ((uintptr_t)dst & 0x3)
178+
return NULL;
179+
180+
return __text_poke(text_poke_memset, dst, &insn, len);
181+
}
182+
108183
int __kprobes aarch64_insn_patch_text_nosync(void *addr, u32 insn)
109184
{
110185
u32 *tp = addr;

arch/arm64/net/bpf_jit_comp.c

Lines changed: 115 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ struct jit_ctx {
7676
int *offset;
7777
int exentry_idx;
7878
__le32 *image;
79+
__le32 *ro_image;
7980
u32 stack_size;
8081
int fpb_offset;
8182
};
@@ -205,6 +206,14 @@ static void jit_fill_hole(void *area, unsigned int size)
205206
*ptr++ = cpu_to_le32(AARCH64_BREAK_FAULT);
206207
}
207208

209+
int bpf_arch_text_invalidate(void *dst, size_t len)
210+
{
211+
if (!aarch64_insn_set(dst, AARCH64_BREAK_FAULT, len))
212+
return -EINVAL;
213+
214+
return 0;
215+
}
216+
208217
static inline int epilogue_offset(const struct jit_ctx *ctx)
209218
{
210219
int to = ctx->epilogue_offset;
@@ -746,7 +755,8 @@ static int add_exception_handler(const struct bpf_insn *insn,
746755
struct jit_ctx *ctx,
747756
int dst_reg)
748757
{
749-
off_t offset;
758+
off_t ins_offset;
759+
off_t fixup_offset;
750760
unsigned long pc;
751761
struct exception_table_entry *ex;
752762

@@ -763,12 +773,17 @@ static int add_exception_handler(const struct bpf_insn *insn,
763773
return -EINVAL;
764774

765775
ex = &ctx->prog->aux->extable[ctx->exentry_idx];
766-
pc = (unsigned long)&ctx->image[ctx->idx - 1];
776+
pc = (unsigned long)&ctx->ro_image[ctx->idx - 1];
767777

768-
offset = pc - (long)&ex->insn;
769-
if (WARN_ON_ONCE(offset >= 0 || offset < INT_MIN))
778+
/*
779+
* This is the relative offset of the instruction that may fault from
780+
* the exception table itself. This will be written to the exception
781+
* table and if this instruction faults, the destination register will
782+
* be set to '0' and the execution will jump to the next instruction.
783+
*/
784+
ins_offset = pc - (long)&ex->insn;
785+
if (WARN_ON_ONCE(ins_offset >= 0 || ins_offset < INT_MIN))
770786
return -ERANGE;
771-
ex->insn = offset;
772787

773788
/*
774789
* Since the extable follows the program, the fixup offset is always
@@ -777,12 +792,25 @@ static int add_exception_handler(const struct bpf_insn *insn,
777792
* bits. We don't need to worry about buildtime or runtime sort
778793
* modifying the upper bits because the table is already sorted, and
779794
* isn't part of the main exception table.
795+
*
796+
* The fixup_offset is set to the next instruction from the instruction
797+
* that may fault. The execution will jump to this after handling the
798+
* fault.
780799
*/
781-
offset = (long)&ex->fixup - (pc + AARCH64_INSN_SIZE);
782-
if (!FIELD_FIT(BPF_FIXUP_OFFSET_MASK, offset))
800+
fixup_offset = (long)&ex->fixup - (pc + AARCH64_INSN_SIZE);
801+
if (!FIELD_FIT(BPF_FIXUP_OFFSET_MASK, fixup_offset))
783802
return -ERANGE;
784803

785-
ex->fixup = FIELD_PREP(BPF_FIXUP_OFFSET_MASK, offset) |
804+
/*
805+
* The offsets above have been calculated using the RO buffer but we
806+
* need to use the R/W buffer for writes.
807+
* switch ex to rw buffer for writing.
808+
*/
809+
ex = (void *)ctx->image + ((void *)ex - (void *)ctx->ro_image);
810+
811+
ex->insn = ins_offset;
812+
813+
ex->fixup = FIELD_PREP(BPF_FIXUP_OFFSET_MASK, fixup_offset) |
786814
FIELD_PREP(BPF_FIXUP_REG_MASK, dst_reg);
787815

788816
ex->type = EX_TYPE_BPF;
@@ -1550,7 +1578,8 @@ static inline void bpf_flush_icache(void *start, void *end)
15501578

15511579
struct arm64_jit_data {
15521580
struct bpf_binary_header *header;
1553-
u8 *image;
1581+
u8 *ro_image;
1582+
struct bpf_binary_header *ro_header;
15541583
struct jit_ctx ctx;
15551584
};
15561585

@@ -1559,12 +1588,14 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog)
15591588
int image_size, prog_size, extable_size, extable_align, extable_offset;
15601589
struct bpf_prog *tmp, *orig_prog = prog;
15611590
struct bpf_binary_header *header;
1591+
struct bpf_binary_header *ro_header;
15621592
struct arm64_jit_data *jit_data;
15631593
bool was_classic = bpf_prog_was_classic(prog);
15641594
bool tmp_blinded = false;
15651595
bool extra_pass = false;
15661596
struct jit_ctx ctx;
15671597
u8 *image_ptr;
1598+
u8 *ro_image_ptr;
15681599

15691600
if (!prog->jit_requested)
15701601
return orig_prog;
@@ -1591,8 +1622,11 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog)
15911622
}
15921623
if (jit_data->ctx.offset) {
15931624
ctx = jit_data->ctx;
1594-
image_ptr = jit_data->image;
1625+
ro_image_ptr = jit_data->ro_image;
1626+
ro_header = jit_data->ro_header;
15951627
header = jit_data->header;
1628+
image_ptr = (void *)header + ((void *)ro_image_ptr
1629+
- (void *)ro_header);
15961630
extra_pass = true;
15971631
prog_size = sizeof(u32) * ctx.idx;
15981632
goto skip_init_ctx;
@@ -1637,63 +1671,81 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog)
16371671
/* also allocate space for plt target */
16381672
extable_offset = round_up(prog_size + PLT_TARGET_SIZE, extable_align);
16391673
image_size = extable_offset + extable_size;
1640-
header = bpf_jit_binary_alloc(image_size, &image_ptr,
1641-
sizeof(u32), jit_fill_hole);
1642-
if (header == NULL) {
1674+
ro_header = bpf_jit_binary_pack_alloc(image_size, &ro_image_ptr,
1675+
sizeof(u32), &header, &image_ptr,
1676+
jit_fill_hole);
1677+
if (!ro_header) {
16431678
prog = orig_prog;
16441679
goto out_off;
16451680
}
16461681

16471682
/* 2. Now, the actual pass. */
16481683

1684+
/*
1685+
* Use the image(RW) for writing the JITed instructions. But also save
1686+
* the ro_image(RX) for calculating the offsets in the image. The RW
1687+
* image will be later copied to the RX image from where the program
1688+
* will run. The bpf_jit_binary_pack_finalize() will do this copy in the
1689+
* final step.
1690+
*/
16491691
ctx.image = (__le32 *)image_ptr;
1692+
ctx.ro_image = (__le32 *)ro_image_ptr;
16501693
if (extable_size)
1651-
prog->aux->extable = (void *)image_ptr + extable_offset;
1694+
prog->aux->extable = (void *)ro_image_ptr + extable_offset;
16521695
skip_init_ctx:
16531696
ctx.idx = 0;
16541697
ctx.exentry_idx = 0;
16551698

16561699
build_prologue(&ctx, was_classic, prog->aux->exception_cb);
16571700

16581701
if (build_body(&ctx, extra_pass)) {
1659-
bpf_jit_binary_free(header);
16601702
prog = orig_prog;
1661-
goto out_off;
1703+
goto out_free_hdr;
16621704
}
16631705

16641706
build_epilogue(&ctx, prog->aux->exception_cb);
16651707
build_plt(&ctx);
16661708

16671709
/* 3. Extra pass to validate JITed code. */
16681710
if (validate_ctx(&ctx)) {
1669-
bpf_jit_binary_free(header);
16701711
prog = orig_prog;
1671-
goto out_off;
1712+
goto out_free_hdr;
16721713
}
16731714

16741715
/* And we're done. */
16751716
if (bpf_jit_enable > 1)
16761717
bpf_jit_dump(prog->len, prog_size, 2, ctx.image);
16771718

1678-
bpf_flush_icache(header, ctx.image + ctx.idx);
1679-
16801719
if (!prog->is_func || extra_pass) {
16811720
if (extra_pass && ctx.idx != jit_data->ctx.idx) {
16821721
pr_err_once("multi-func JIT bug %d != %d\n",
16831722
ctx.idx, jit_data->ctx.idx);
1684-
bpf_jit_binary_free(header);
16851723
prog->bpf_func = NULL;
16861724
prog->jited = 0;
16871725
prog->jited_len = 0;
1726+
goto out_free_hdr;
1727+
}
1728+
if (WARN_ON(bpf_jit_binary_pack_finalize(prog, ro_header,
1729+
header))) {
1730+
/* ro_header has been freed */
1731+
ro_header = NULL;
1732+
prog = orig_prog;
16881733
goto out_off;
16891734
}
1690-
bpf_jit_binary_lock_ro(header);
1735+
/*
1736+
* The instructions have now been copied to the ROX region from
1737+
* where they will execute. Now the data cache has to be cleaned to
1738+
* the PoU and the I-cache has to be invalidated for the VAs.
1739+
*/
1740+
bpf_flush_icache(ro_header, ctx.ro_image + ctx.idx);
16911741
} else {
16921742
jit_data->ctx = ctx;
1693-
jit_data->image = image_ptr;
1743+
jit_data->ro_image = ro_image_ptr;
16941744
jit_data->header = header;
1745+
jit_data->ro_header = ro_header;
16951746
}
1696-
prog->bpf_func = (void *)ctx.image;
1747+
1748+
prog->bpf_func = (void *)ctx.ro_image;
16971749
prog->jited = 1;
16981750
prog->jited_len = prog_size;
16991751

@@ -1714,13 +1766,28 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog)
17141766
bpf_jit_prog_release_other(prog, prog == orig_prog ?
17151767
tmp : orig_prog);
17161768
return prog;
1769+
1770+
out_free_hdr:
1771+
if (header) {
1772+
bpf_arch_text_copy(&ro_header->size, &header->size,
1773+
sizeof(header->size));
1774+
bpf_jit_binary_pack_free(ro_header, header);
1775+
}
1776+
goto out_off;
17171777
}
17181778

17191779
bool bpf_jit_supports_kfunc_call(void)
17201780
{
17211781
return true;
17221782
}
17231783

1784+
void *bpf_arch_text_copy(void *dst, void *src, size_t len)
1785+
{
1786+
if (!aarch64_insn_copy(dst, src, len))
1787+
return ERR_PTR(-EINVAL);
1788+
return dst;
1789+
}
1790+
17241791
u64 bpf_jit_alloc_exec_limit(void)
17251792
{
17261793
return VMALLOC_END - VMALLOC_START;
@@ -2359,3 +2426,27 @@ bool bpf_jit_supports_exceptions(void)
23592426
*/
23602427
return true;
23612428
}
2429+
2430+
void bpf_jit_free(struct bpf_prog *prog)
2431+
{
2432+
if (prog->jited) {
2433+
struct arm64_jit_data *jit_data = prog->aux->jit_data;
2434+
struct bpf_binary_header *hdr;
2435+
2436+
/*
2437+
* If we fail the final pass of JIT (from jit_subprogs),
2438+
* the program may not be finalized yet. Call finalize here
2439+
* before freeing it.
2440+
*/
2441+
if (jit_data) {
2442+
bpf_arch_text_copy(&jit_data->ro_header->size, &jit_data->header->size,
2443+
sizeof(jit_data->header->size));
2444+
kfree(jit_data);
2445+
}
2446+
hdr = bpf_jit_binary_pack_hdr(prog);
2447+
bpf_jit_binary_pack_free(hdr, NULL);
2448+
WARN_ON_ONCE(!bpf_prog_kallsyms_verify_off(prog));
2449+
}
2450+
2451+
bpf_prog_unlock_free(prog);
2452+
}

0 commit comments

Comments
 (0)