[PATCH v2 6/7] app/test: add test for bpf validate debug events

Marat Khalili marat.khalili at huawei.com
Mon Sep 14 16:54:16 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 | 341 ++++++++++++++++++++++++++++++++++-
 1 file changed, 337 insertions(+), 4 deletions(-)

diff --git a/app/test/test_bpf_validate.c b/app/test/test_bpf_validate.c
index 066f1fa156ae..ea4621b257f4 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,343 @@ 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
+	 *
+	 * 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