The original bcc pull request https://github.com/iovisor/bcc/pull/3270 exposed a verifier failure with Clang 12/13 while Clang 4 works fine. Further investigation exposed two issues: Issue 1: LLVM may generate code which uses less refined value. The issue is fixed in LLVM patch: https://reviews.llvm.org/D97479 Issue 2: Spills with initial value 0 are marked as precise which makes later state pruning less effective. This is my rough initial analysis and further investigation is needed to find how to improve verifier pruning in such cases. With the above LLVM patch, for the new loop6.c test, which has smaller loop bound compared to original test, I got: $ test_progs -s -n 10/16 ... stack depth 64 processed 390735 insns (limit 1000000) max_states_per_insn 87 total_states 8658 peak_states 964 mark_read 6 #10/16 loop6.o:OK Use the original loop bound, i.e., commenting out "#define WORKAROUND", I got: $ test_progs -s -n 10/16 ... BPF program is too large. Processed 1000001 insn stack depth 64 processed 1000001 insns (limit 1000000) max_states_per_insn 91 total_states 23176 peak_states 5069 mark_read 6 ... #10/16 loop6.o:FAIL The purpose of this patch is to provide a regression test for the above LLVM fix and also provide a test case for further analyzing the verifier pruning issue. Signed-off-by: Yonghong Song <yhs@fb.com> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net> Cc: Zhenwei Pi <pizhenwei@bytedance.com> Link: https://lore.kernel.org/bpf/20210226223810.236472-1-yhs@fb.com
121 lines
3.2 KiB
C
121 lines
3.2 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
// Copyright (c) 2019 Facebook
|
|
#include <test_progs.h>
|
|
static int libbpf_debug_print(enum libbpf_print_level level,
|
|
const char *format, va_list args)
|
|
{
|
|
if (level != LIBBPF_DEBUG) {
|
|
vprintf(format, args);
|
|
return 0;
|
|
}
|
|
|
|
if (!strstr(format, "verifier log"))
|
|
return 0;
|
|
vprintf("%s", args);
|
|
return 0;
|
|
}
|
|
|
|
extern int extra_prog_load_log_flags;
|
|
|
|
static int check_load(const char *file, enum bpf_prog_type type)
|
|
{
|
|
struct bpf_prog_load_attr attr;
|
|
struct bpf_object *obj = NULL;
|
|
int err, prog_fd;
|
|
|
|
memset(&attr, 0, sizeof(struct bpf_prog_load_attr));
|
|
attr.file = file;
|
|
attr.prog_type = type;
|
|
attr.log_level = 4 | extra_prog_load_log_flags;
|
|
attr.prog_flags = BPF_F_TEST_RND_HI32;
|
|
err = bpf_prog_load_xattr(&attr, &obj, &prog_fd);
|
|
bpf_object__close(obj);
|
|
return err;
|
|
}
|
|
|
|
struct scale_test_def {
|
|
const char *file;
|
|
enum bpf_prog_type attach_type;
|
|
bool fails;
|
|
};
|
|
|
|
void test_bpf_verif_scale(void)
|
|
{
|
|
struct scale_test_def tests[] = {
|
|
{ "loop3.o", BPF_PROG_TYPE_RAW_TRACEPOINT, true /* fails */ },
|
|
|
|
{ "test_verif_scale1.o", BPF_PROG_TYPE_SCHED_CLS },
|
|
{ "test_verif_scale2.o", BPF_PROG_TYPE_SCHED_CLS },
|
|
{ "test_verif_scale3.o", BPF_PROG_TYPE_SCHED_CLS },
|
|
|
|
{ "pyperf_global.o", BPF_PROG_TYPE_RAW_TRACEPOINT },
|
|
{ "pyperf_subprogs.o", BPF_PROG_TYPE_RAW_TRACEPOINT },
|
|
|
|
/* full unroll by llvm */
|
|
{ "pyperf50.o", BPF_PROG_TYPE_RAW_TRACEPOINT },
|
|
{ "pyperf100.o", BPF_PROG_TYPE_RAW_TRACEPOINT },
|
|
{ "pyperf180.o", BPF_PROG_TYPE_RAW_TRACEPOINT },
|
|
|
|
/* partial unroll. llvm will unroll loop ~150 times.
|
|
* C loop count -> 600.
|
|
* Asm loop count -> 4.
|
|
* 16k insns in loop body.
|
|
* Total of 5 such loops. Total program size ~82k insns.
|
|
*/
|
|
{ "pyperf600.o", BPF_PROG_TYPE_RAW_TRACEPOINT },
|
|
|
|
/* no unroll at all.
|
|
* C loop count -> 600.
|
|
* ASM loop count -> 600.
|
|
* ~110 insns in loop body.
|
|
* Total of 5 such loops. Total program size ~1500 insns.
|
|
*/
|
|
{ "pyperf600_nounroll.o", BPF_PROG_TYPE_RAW_TRACEPOINT },
|
|
|
|
{ "loop1.o", BPF_PROG_TYPE_RAW_TRACEPOINT },
|
|
{ "loop2.o", BPF_PROG_TYPE_RAW_TRACEPOINT },
|
|
{ "loop4.o", BPF_PROG_TYPE_SCHED_CLS },
|
|
{ "loop5.o", BPF_PROG_TYPE_SCHED_CLS },
|
|
{ "loop6.o", BPF_PROG_TYPE_KPROBE },
|
|
|
|
/* partial unroll. 19k insn in a loop.
|
|
* Total program size 20.8k insn.
|
|
* ~350k processed_insns
|
|
*/
|
|
{ "strobemeta.o", BPF_PROG_TYPE_RAW_TRACEPOINT },
|
|
|
|
/* no unroll, tiny loops */
|
|
{ "strobemeta_nounroll1.o", BPF_PROG_TYPE_RAW_TRACEPOINT },
|
|
{ "strobemeta_nounroll2.o", BPF_PROG_TYPE_RAW_TRACEPOINT },
|
|
|
|
/* non-inlined subprogs */
|
|
{ "strobemeta_subprogs.o", BPF_PROG_TYPE_RAW_TRACEPOINT },
|
|
|
|
{ "test_sysctl_loop1.o", BPF_PROG_TYPE_CGROUP_SYSCTL },
|
|
{ "test_sysctl_loop2.o", BPF_PROG_TYPE_CGROUP_SYSCTL },
|
|
|
|
{ "test_xdp_loop.o", BPF_PROG_TYPE_XDP },
|
|
{ "test_seg6_loop.o", BPF_PROG_TYPE_LWT_SEG6LOCAL },
|
|
};
|
|
libbpf_print_fn_t old_print_fn = NULL;
|
|
int err, i;
|
|
|
|
if (env.verifier_stats) {
|
|
test__force_log();
|
|
old_print_fn = libbpf_set_print(libbpf_debug_print);
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(tests); i++) {
|
|
const struct scale_test_def *test = &tests[i];
|
|
|
|
if (!test__start_subtest(test->file))
|
|
continue;
|
|
|
|
err = check_load(test->file, test->attach_type);
|
|
CHECK_FAIL(err && !test->fails);
|
|
}
|
|
|
|
if (env.verifier_stats)
|
|
libbpf_set_print(old_print_fn);
|
|
}
|