[PATCH v3 6/7] app/test: add test for bpf validate debug events
Marat Khalili
marat.khalili at huawei.com
Tue Sep 15 21:26:24 CEST 2026
Add a test that verifies that eBPF validator generates callbacks with
the number, order and program counter specified in the documentation.
Signed-off-by: Marat Khalili <marat.khalili at huawei.com>
---
app/test/test_bpf_validate.c | 344 ++++++++++++++++++++++++++++++++++-
1 file changed, 340 insertions(+), 4 deletions(-)
diff --git a/app/test/test_bpf_validate.c b/app/test/test_bpf_validate.c
index 066f1fa156ae..8b48d8a504dd 100644
--- a/app/test/test_bpf_validate.c
+++ b/app/test/test_bpf_validate.c
@@ -9,10 +9,6 @@
#include <rte_bpf_validate_debug.h>
#include <rte_errno.h>
-/*
- * Tests of BPF validation.
- */
-
extern int test_bpf_validate_logtype;
#define RTE_LOGTYPE_TEST_BPF_VALIDATE test_bpf_validate_logtype
#define TEST_LOG_LINE(level, ...) \
@@ -26,6 +22,346 @@ RTE_LOG_REGISTER(test_bpf_validate_logtype, test.bpf_validate, NOTICE);
/* Special value indicating that register variable is not being used. */
#define NO_REGISTER UINT8_MAX
+static const char *const event_names[RTE_BPF_VALIDATE_DEBUG_EVENT_END] = {
+ [RTE_BPF_VALIDATE_DEBUG_EVENT_STEP] = "step",
+ [RTE_BPF_VALIDATE_DEBUG_EVENT_INVALID_STATE] = "invalid-state",
+ [RTE_BPF_VALIDATE_DEBUG_EVENT_VALIDATION_START] = "validation-start",
+ [RTE_BPF_VALIDATE_DEBUG_EVENT_VALIDATION_SUCCESS] = "validation-success",
+ [RTE_BPF_VALIDATE_DEBUG_EVENT_VALIDATION_FAILURE] = "validation-failure",
+ [RTE_BPF_VALIDATE_DEBUG_EVENT_BRANCH_ENTER] = "branch-enter",
+ [RTE_BPF_VALIDATE_DEBUG_EVENT_BRANCH_PRUNE] = "branch-prune",
+ [RTE_BPF_VALIDATE_DEBUG_EVENT_BRANCH_RETURN] = "branch-return",
+ [RTE_BPF_VALIDATE_DEBUG_EVENT_BRANCH_UNREACHABLE] = "branch-unreachable",
+ [RTE_BPF_VALIDATE_DEBUG_EVENT_JUMP_ALWAYS] = "jump-always",
+ [RTE_BPF_VALIDATE_DEBUG_EVENT_JUMP_CONDITIONAL] = "jump-conditional",
+};
+
+/*
+ * Self-test of BPF validation debug events.
+ */
+
+#define EVENT_BIT(name) RTE_BIT64(RTE_BPF_VALIDATE_DEBUG_EVENT_ ## name)
+#define EVENT_BITS_ALL RTE_LEN2MASK(RTE_BPF_VALIDATE_DEBUG_EVENT_END, uint64_t)
+
+struct test_events_context {
+ /* Tested program. */
+ const struct ebpf_insn *ins;
+ uint32_t nb_ins;
+ /* Number of times each of the events was encountered. */
+ int event_counts[RTE_BPF_VALIDATE_DEBUG_EVENT_END];
+ /* Events active within the current step. */
+ uint64_t active_events;
+ /* Breakpoint was called within the current step. */
+ bool breakpoint_called;
+ /* Currently active conditional jump pc. */
+ uint32_t branch_pc;
+ /* Program counters of all conditional jumps in the current code path. */
+ uint32_t branch_pc_stack[4];
+ size_t branch_pc_stack_length;
+};
+
+/* Push current branch_pc to stack and clear it. */
+static int
+test_events_branch_pc_push(struct test_events_context *ctx)
+{
+ TEST_ASSERT_NOT_EQUAL(ctx->branch_pc, NO_PROGRAM_COUNTER,
+ "no branch pc to push");
+ TEST_ASSERT(ctx->branch_pc_stack_length < RTE_DIM(ctx->branch_pc_stack),
+ "branch stack overflow");
+ ctx->branch_pc_stack[ctx->branch_pc_stack_length++] = ctx->branch_pc;
+ ctx->branch_pc = NO_PROGRAM_COUNTER;
+ return TEST_SUCCESS;
+}
+
+/* Pop branch_pc from stack. */
+static int
+test_events_branch_pc_pop(struct test_events_context *ctx)
+{
+ TEST_ASSERT(ctx->branch_pc_stack_length > 0, "branch stack underflow");
+ ctx->branch_pc = ctx->branch_pc_stack[--ctx->branch_pc_stack_length];
+ return TEST_SUCCESS;
+}
+
+/* Verify breakpoint, checking that it is only called once and remembering it. */
+static int
+test_events_break_cb(struct rte_bpf_validate_debug *debug, void *void_ctx)
+{
+ struct test_events_context *const ctx = void_ctx;
+ const uint32_t pc = rte_bpf_validate_debug_get_pc(debug);
+
+ TEST_LOG_LINE(DEBUG, "%2u: break", pc);
+
+ TEST_ASSERT(!ctx->breakpoint_called,
+ "breakpoint called again at pc %u", pc);
+ ctx->breakpoint_called = true;
+
+ return TEST_SUCCESS;
+}
+
+/*
+ * Verify event, checking that:
+ * - validation-start is called at pc 0;
+ * - branch-return is called at a corresponding conditional jump pc;
+ * - branch-enter and branch-return are correctly paired;
+ * - jump-conditional and jump-always are called for corresponding opcodes;
+ * - jump-conditional and only it is followed by branches events;
+ * - events and breakpoints are correctly ordered;
+ *
+ * Increment event counter to compare against expected counts later.
+ */
+static int
+test_events_catch_cb(struct rte_bpf_validate_debug *debug, void *void_ctx)
+{
+ /* Bitmask of events allowed to precede each given event within the step. */
+ static const uint64_t allowed_preceding_events[] = {
+ [RTE_BPF_VALIDATE_DEBUG_EVENT_VALIDATION_START] = 0,
+ [RTE_BPF_VALIDATE_DEBUG_EVENT_BRANCH_PRUNE] =
+ EVENT_BIT(BRANCH_PRUNE) |
+ EVENT_BIT(BRANCH_UNREACHABLE) |
+ EVENT_BIT(BRANCH_RETURN),
+ [RTE_BPF_VALIDATE_DEBUG_EVENT_BRANCH_UNREACHABLE] =
+ EVENT_BIT(BRANCH_PRUNE) |
+ EVENT_BIT(BRANCH_UNREACHABLE) |
+ EVENT_BIT(BRANCH_RETURN),
+ [RTE_BPF_VALIDATE_DEBUG_EVENT_BRANCH_ENTER] =
+ EVENT_BIT(BRANCH_PRUNE) |
+ EVENT_BIT(BRANCH_UNREACHABLE) |
+ EVENT_BIT(BRANCH_RETURN),
+ [RTE_BPF_VALIDATE_DEBUG_EVENT_BRANCH_RETURN] =
+ EVENT_BIT(BRANCH_PRUNE) |
+ EVENT_BIT(BRANCH_UNREACHABLE) |
+ EVENT_BIT(BRANCH_RETURN),
+ [RTE_BPF_VALIDATE_DEBUG_EVENT_JUMP_CONDITIONAL] =
+ EVENT_BIT(BRANCH_ENTER) |
+ EVENT_BIT(BRANCH_PRUNE) |
+ EVENT_BIT(BRANCH_UNREACHABLE) |
+ EVENT_BIT(BRANCH_RETURN),
+ [RTE_BPF_VALIDATE_DEBUG_EVENT_JUMP_ALWAYS] =
+ EVENT_BIT(BRANCH_ENTER) |
+ EVENT_BIT(BRANCH_PRUNE) |
+ EVENT_BIT(BRANCH_UNREACHABLE) |
+ EVENT_BIT(BRANCH_RETURN),
+ [RTE_BPF_VALIDATE_DEBUG_EVENT_STEP] =
+ EVENT_BITS_ALL - EVENT_BIT(VALIDATION_SUCCESS),
+ [RTE_BPF_VALIDATE_DEBUG_EVENT_VALIDATION_SUCCESS] =
+ EVENT_BIT(BRANCH_PRUNE) |
+ EVENT_BIT(BRANCH_UNREACHABLE) |
+ EVENT_BIT(BRANCH_RETURN),
+ };
+
+ struct test_events_context *const ctx = void_ctx;
+ const enum rte_bpf_validate_debug_event event =
+ rte_bpf_validate_debug_get_event(debug);
+ const uint32_t pc = rte_bpf_validate_debug_get_pc(debug);
+
+ TEST_LOG_LINE(DEBUG, "%2u: %s", pc, event_names[event]);
+
+ /* Verify event program counter and underlying instruction. */
+ switch (event) {
+ case RTE_BPF_VALIDATE_DEBUG_EVENT_VALIDATION_START:
+ TEST_ASSERT_EQUAL(pc, 0,
+ "event %s called at pc %u",
+ event_names[event], pc);
+ break;
+ case RTE_BPF_VALIDATE_DEBUG_EVENT_BRANCH_ENTER:
+ TEST_ASSERT_SUCCESS(test_events_branch_pc_push(ctx),
+ "error pushing branch for event %s at pc %u",
+ event_names[event], pc);
+ break;
+ case RTE_BPF_VALIDATE_DEBUG_EVENT_BRANCH_RETURN:
+ TEST_ASSERT_SUCCESS(test_events_branch_pc_pop(ctx),
+ "error popping branch for event %s at pc %u",
+ event_names[event], pc);
+ TEST_ASSERT_EQUAL(pc, ctx->branch_pc,
+ "event %s called at pc %u after jump pc %u",
+ event_names[event], pc, ctx->branch_pc);
+ break;
+ case RTE_BPF_VALIDATE_DEBUG_EVENT_JUMP_ALWAYS:
+ TEST_ASSERT_EQUAL(BPF_CLASS(ctx->ins[pc].code), BPF_JMP,
+ "event %s called at non-jump pc %u",
+ event_names[event], pc);
+ TEST_ASSERT_EQUAL(BPF_OP(ctx->ins[pc].code), BPF_JA,
+ "event %s called at non-jump-always pc %u",
+ event_names[event], pc);
+ break;
+ case RTE_BPF_VALIDATE_DEBUG_EVENT_JUMP_CONDITIONAL:
+ TEST_ASSERT_EQUAL(BPF_CLASS(ctx->ins[pc].code), BPF_JMP,
+ "event %s called at non-jump pc %u",
+ event_names[event], pc);
+ TEST_ASSERT_NOT_EQUAL(BPF_OP(ctx->ins[pc].code), BPF_JA,
+ "event %s called at jump-always pc %u",
+ event_names[event], pc);
+ break;
+ default:
+ break;
+ }
+
+ /* Verify events order. */
+ TEST_ASSERT_EQUAL(
+ (ctx->active_events & ~allowed_preceding_events[event]), 0,
+ "event %s at pc %u preceded by %#jx",
+ event_names[event], pc, (uintmax_t)ctx->active_events);
+
+ /* Update events for the current step as well as branch program counter. */
+ if (event != RTE_BPF_VALIDATE_DEBUG_EVENT_STEP) {
+ TEST_ASSERT(!ctx->breakpoint_called,
+ "event %s called after a breakpoint at pc %u",
+ event_names[event], pc);
+ ctx->active_events |= RTE_BIT64(event);
+ } else {
+ TEST_ASSERT_EQUAL(ctx->branch_pc, NO_PROGRAM_COUNTER,
+ "no branch-enter between branch- and next instruction");
+
+ if (ctx->active_events & EVENT_BIT(JUMP_CONDITIONAL))
+ /* Remember branch instruction pointer, branches to follow. */
+ ctx->branch_pc = pc;
+
+ TEST_ASSERT(ctx->breakpoint_called,
+ "step called before or without a breakpoint at pc %u", pc);
+
+ /* Prepare for the next step. */
+ ctx->active_events = 0;
+ ctx->breakpoint_called = false;
+ }
+
+ /* Update total event counts. */
+ ++ctx->event_counts[event];
+
+ return TEST_SUCCESS;
+}
+
+/*
+ * This test verifies that eBPF validator generates expected numbers of events
+ * of each kind in the expected order (e.g. step after jump after branch).
+ */
+static int
+test_events(void)
+{
+ /*
+ * Program demonstrating the following properties:
+ * - prunnable code paths;
+ * - dynamically unreachable code paths;
+ * - conditional jumps landing on conditional jumps;
+ * - conditional jumps landing on unconditional jumps;
+ */
+ static const struct ebpf_insn ins[] = {
+ /* 0: r0 = 0 */
+ { .code = (EBPF_ALU64 | EBPF_MOV | BPF_K), .dst_reg = EBPF_REG_0, .imm = 0 },
+ /* 1: if r1 > 0 goto 4 */
+ { .code = (BPF_JMP | BPF_JGT | BPF_K), .dst_reg = EBPF_REG_1, .imm = 0, .off = 2 },
+ /* 2: r1 = 0 */
+ { .code = (EBPF_ALU64 | EBPF_MOV | BPF_K), .dst_reg = EBPF_REG_1, .imm = 0 },
+ /* 3: if r0 < 1 goto 8 */
+ { .code = (BPF_JMP | EBPF_JLT | BPF_K), .dst_reg = EBPF_REG_0, .imm = 1, .off = 4 },
+ /* 4: if r1 > 0 goto 6 */
+ { .code = (BPF_JMP | BPF_JGT | BPF_K), .dst_reg = EBPF_REG_1, .imm = 0, .off = 1 },
+ /* 5: goto 9 */
+ { .code = (BPF_JMP | BPF_JA), .off = 3 },
+ /* 6: r1 = 0 */
+ { .code = (EBPF_ALU64 | EBPF_MOV | BPF_K), .dst_reg = EBPF_REG_1, .imm = 0 },
+ /* 7: if r0 > 0 goto 9 */
+ { .code = (BPF_JMP | BPF_JGT | BPF_K), .dst_reg = EBPF_REG_0, .imm = 0, .off = 1 },
+ /* 8: goto 10 */
+ { .code = (BPF_JMP | BPF_JA), .off = 1 },
+ /* 9: r0 = 1 */
+ { .code = (EBPF_ALU64 | EBPF_MOV | BPF_K), .dst_reg = EBPF_REG_0, .imm = 1 },
+ /* 10: exit */
+ { .code = (BPF_JMP | EBPF_EXIT) }
+ };
+
+ /*
+ * Verified code paths (order may vary by library implementation):
+ * 0 -- 1 -- 4 -- 6 -- 7 -- unreachable 9
+ * | | `-- 8 -- 10 -- return 7 -- return 4
+ * | `-- unreachable 5 -- return 1
+ * `-- 2 -- 3 -- prune 8
+ * `-- unreachable 4 -- return 1 -- success 11
+ *
+ * Numbers without preceding word correspond to evaluated instructions,
+ * there are 9 of these in total, so 9 step events are expected.
+ *
+ * Note that 4 encountered conditional jump instructions generate:
+ * - 4 pairs of branch-enter/branch-return events;
+ * - 3 branch-unreachable events;
+ * - 1 branch-prune event;
+ * for a total count of 8 (4 instructions * 2 branches/instruction).
+ */
+ static const int expected_counts[RTE_BPF_VALIDATE_DEBUG_EVENT_END] = {
+ [RTE_BPF_VALIDATE_DEBUG_EVENT_STEP] = 9,
+ [RTE_BPF_VALIDATE_DEBUG_EVENT_VALIDATION_START] = 1,
+ [RTE_BPF_VALIDATE_DEBUG_EVENT_VALIDATION_SUCCESS] = 1,
+ [RTE_BPF_VALIDATE_DEBUG_EVENT_BRANCH_ENTER] = 4,
+ [RTE_BPF_VALIDATE_DEBUG_EVENT_BRANCH_PRUNE] = 1,
+ [RTE_BPF_VALIDATE_DEBUG_EVENT_BRANCH_RETURN] = 4,
+ [RTE_BPF_VALIDATE_DEBUG_EVENT_BRANCH_UNREACHABLE] = 3,
+ [RTE_BPF_VALIDATE_DEBUG_EVENT_JUMP_ALWAYS] = 1,
+ [RTE_BPF_VALIDATE_DEBUG_EVENT_JUMP_CONDITIONAL] = 4,
+ };
+
+ struct test_events_context ctx = {
+ .ins = ins,
+ .nb_ins = RTE_DIM(ins),
+ .branch_pc = NO_PROGRAM_COUNTER,
+ };
+
+ struct rte_bpf_validate_debug *debug = rte_bpf_validate_debug_create();
+ TEST_ASSERT_NOT_NULL(debug, "validate debug create error");
+
+ /* Set breakpoints for all instructions. */
+ for (uint32_t pc = 0; pc != RTE_DIM(ins); ++pc)
+ rte_bpf_validate_debug_break(debug, pc,
+ &(struct rte_bpf_validate_debug_callback){
+ .fn = test_events_break_cb,
+ .ctx = &ctx,
+ });
+
+ /* Set catchpoints for all events except invalid-state. */
+ for (enum rte_bpf_validate_debug_event event = 0;
+ event != RTE_BPF_VALIDATE_DEBUG_EVENT_END; ++event)
+ if (event != RTE_BPF_VALIDATE_DEBUG_EVENT_INVALID_STATE)
+ rte_bpf_validate_debug_catch(debug, event,
+ &(struct rte_bpf_validate_debug_callback){
+ .fn = test_events_catch_cb,
+ .ctx = &ctx,
+ });
+
+ struct rte_bpf *const bpf = rte_bpf_load_ex(&(struct rte_bpf_prm_ex){
+ .sz = sizeof(struct rte_bpf_prm_ex),
+ .origin = RTE_BPF_ORIGIN_RAW,
+ .raw.ins = ins,
+ .raw.nb_ins = RTE_DIM(ins),
+ .prog_arg[0] = { .type = RTE_BPF_ARG_RAW },
+ .nb_prog_arg = 1,
+ .debug = debug,
+ });
+
+ rte_bpf_destroy(bpf);
+ rte_bpf_validate_debug_destroy(debug);
+
+ TEST_ASSERT_NOT_NULL(bpf, "validation failed");
+
+ /* Verify event counts. */
+ for (enum rte_bpf_validate_debug_event event = 0;
+ event != RTE_BPF_VALIDATE_DEBUG_EVENT_END; ++event)
+ TEST_ASSERT_EQUAL(
+ ctx.event_counts[event], expected_counts[event],
+ "expected %d %s events, but got %d",
+ expected_counts[event], event_names[event],
+ ctx.event_counts[event]);
+
+ /* Additionally double-check final stack state. */
+ TEST_ASSERT_EQUAL(ctx.branch_pc_stack_length, 0,
+ "unbalanced branch enters and returns");
+
+ return TEST_SUCCESS;
+}
+
+REGISTER_FAST_TEST(bpf_validate_events_autotest, NOHUGE_OK, ASAN_OK, test_events);
+
+/*
+ * Tests of BPF validation.
+ */
+
/* Sizes of text buffers used for formatting various debug outputs. */
#define VALUE_FORMAT_BUFFER_SIZE 32
#define INTERVAL_FORMAT_BUFFER_SIZE 64
--
2.43.0
More information about the dev
mailing list