[PATCH 3/7] bpf/validate: formalize call back requirements

Marat Khalili marat.khalili at huawei.com
Fri Sep 11 12:40:01 CEST 2026


Formalize requirements to evaluation events in documentation and adjust
their handling in the actual code to match.

Particularly, specify more precisely:
* conditions for each catchpoint callback;
* relative order of callbacks within the step;
* expected value of the program counter;

Behaviour changes:
* one branch-return event is now generated for each branch-enter event
  (previously multiple branch-return events could be coalesced);
* branch-return event now points to the corresponding conditional jump
  (previously past the end of the last branch);
* step event is now only generated before an actual instruction
  evaluation (previously also for branching and finishing events);
* program counter is no longer allowed to point past the program end;

These changes should simplify branch tracking of the validator by tests
and interactive applications. E.g. branch-enter and branch-return events
can now be used to push and pop branching points to/from some stack.

Signed-off-by: Marat Khalili <marat.khalili at huawei.com>
---
 lib/bpf/bpf_validate.c           | 28 +++++++++----
 lib/bpf/bpf_validate_debug.c     | 72 +++++++++++++++++++-------------
 lib/bpf/bpf_validate_debug.h     |  1 -
 lib/bpf/rte_bpf_validate_debug.h | 24 +++++++----
 4 files changed, 77 insertions(+), 48 deletions(-)

diff --git a/lib/bpf/bpf_validate.c b/lib/bpf/bpf_validate.c
index b317abb81172..f4258c2fb54f 100644
--- a/lib/bpf/bpf_validate.c
+++ b/lib/bpf/bpf_validate.c
@@ -2892,6 +2892,23 @@ prune_eval_state(struct bpf_verifier *bvf, const struct inst_node *node,
 	return rc;
 }
 
+static bool
+is_branch_start(const struct inst_node *node)
+{
+	return node->prev_node != NULL && node->prev_node->nb_edge > 1;
+}
+
+static uint64_t
+step_events(const struct inst_node *node)
+{
+	uint64_t events = RTE_BIT64(RTE_BPF_VALIDATE_DEBUG_EVENT_STEP);
+
+	if (is_branch_start(node))
+		events |= RTE_BIT64(RTE_BPF_VALIDATE_DEBUG_EVENT_BRANCH_ENTER);
+
+	return events;
+}
+
 /* Do second pass through CFG and try to evaluate instructions
  * via each possible path. The verifier will try all paths, tracking types of
  * registers used as input to instructions, and updating resulting type via
@@ -2918,7 +2935,6 @@ evaluate(struct bpf_verifier *bvf)
 	const char *err;
 	const struct ebpf_insn *ins;
 	struct inst_node *next, *node;
-	int prev_nb_edge;  /* branching number of the previous instruction */
 	int rc, debug_rc;
 	struct rte_bpf_validate_debug *const debug = bvf->prm->debug;
 
@@ -2954,7 +2970,6 @@ evaluate(struct bpf_verifier *bvf)
 	ins = bvf->prm->raw.ins;
 	node = bvf->in;
 	next = node;
-	prev_nb_edge = 1;
 
 	memset(&stats, 0, sizeof(stats));
 
@@ -2990,8 +3005,7 @@ evaluate(struct bpf_verifier *bvf)
 			}
 
 			rc = __rte_bpf_validate_debug_evaluate_update(debug, idx,
-				prev_nb_edge > 1 ?
-					RTE_BIT64(RTE_BPF_VALIDATE_DEBUG_EVENT_BRANCH_ENTER) : 0);
+				step_events(node));
 			if (rc < 0)
 				break;
 
@@ -3047,7 +3061,6 @@ evaluate(struct bpf_verifier *bvf)
 				stats.nb_prune++;
 			} else {
 				next->prev_node = node;
-				prev_nb_edge = node->nb_edge;
 				node = next;
 			}
 		} else {
@@ -3057,9 +3070,9 @@ evaluate(struct bpf_verifier *bvf)
 			 * and proceed with parent.
 			 */
 
-			if (prev_nb_edge != 0) {
+			if (is_branch_start(node)) {
 				rc = __rte_bpf_validate_debug_evaluate_update(
-					debug, get_node_idx(bvf, node) + 1,
+					debug, get_node_idx(bvf, node->prev_node),
 					RTE_BIT64(RTE_BPF_VALIDATE_DEBUG_EVENT_BRANCH_RETURN));
 				if (rc < 0)
 					break;
@@ -3067,7 +3080,6 @@ evaluate(struct bpf_verifier *bvf)
 
 			node->cur_edge = 0;
 			save_safe_eval_state(bvf, node);
-			prev_nb_edge = 0;
 			node = node->prev_node;
 
 			/* first node will not have prev, signalling finish */
diff --git a/lib/bpf/bpf_validate_debug.c b/lib/bpf/bpf_validate_debug.c
index f76303a7fd2a..db6d13f9cb22 100644
--- a/lib/bpf/bpf_validate_debug.c
+++ b/lib/bpf/bpf_validate_debug.c
@@ -225,6 +225,13 @@ debug_points_call_back(struct rte_bpf_validate_debug *debug,
 	return rc;
 }
 
+/* Call back all breakpoints for the specified program counter. */
+static int
+debug_trigger_breakpoints(struct rte_bpf_validate_debug *debug, uint32_t pc)
+{
+	return debug_points_call_back(debug, &debug->breakpoint_lists[pc]);
+}
+
 /* Call back all catchpoints for the specified event. */
 static int
 debug_send_event(struct rte_bpf_validate_debug *debug, debug_event_t event)
@@ -585,6 +592,21 @@ int
 __rte_bpf_validate_debug_evaluate_update(struct rte_bpf_validate_debug *debug,
 	uint32_t pc, uint64_t events)
 {
+	/* Required order of sent events according to the documentation. */
+	static const enum rte_bpf_validate_debug_event ordered_events[] = {
+		RTE_BPF_VALIDATE_DEBUG_EVENT_VALIDATION_START,
+		RTE_BPF_VALIDATE_DEBUG_EVENT_INVALID_STATE,
+		RTE_BPF_VALIDATE_DEBUG_EVENT_BRANCH_ENTER,
+		RTE_BPF_VALIDATE_DEBUG_EVENT_BRANCH_PRUNE,
+		RTE_BPF_VALIDATE_DEBUG_EVENT_BRANCH_RETURN,
+		RTE_BPF_VALIDATE_DEBUG_EVENT_BRANCH_UNREACHABLE,
+		RTE_BPF_VALIDATE_DEBUG_EVENT_STEP,
+		RTE_BPF_VALIDATE_DEBUG_EVENT_VALIDATION_SUCCESS,
+		RTE_BPF_VALIDATE_DEBUG_EVENT_VALIDATION_FAILURE,
+	};
+	RTE_BUILD_BUG_ON(
+		RTE_DIM(ordered_events) != RTE_BPF_VALIDATE_DEBUG_EVENT_END);
+
 	int rc;
 
 	if (debug == NULL)
@@ -595,29 +617,31 @@ __rte_bpf_validate_debug_evaluate_update(struct rte_bpf_validate_debug *debug,
 		return -ECHILD;
 	}
 
-	if (pc > debug->bpf_prm->raw.nb_ins)
+	if (pc >= debug->bpf_prm->raw.nb_ins)
 		return -EINVAL;
 
 	debug->pc = pc;
 
 	rc = __rte_bpf_validate_state_is_valid(debug->verifier);
 	if (rc == 0)
-		rc = debug_send_event(debug,
-			RTE_BPF_VALIDATE_DEBUG_EVENT_INVALID_STATE);
+		events |= RTE_BIT64(RTE_BPF_VALIDATE_DEBUG_EVENT_INVALID_STATE);
+
+	for (uint32_t index = 0; index < RTE_DIM(ordered_events); index++) {
+		const enum rte_bpf_validate_debug_event event =
+			ordered_events[index];
+		if ((events & RTE_BIT64(event)) == 0)
+			continue;
 
-	for (enum rte_bpf_validate_debug_event event = 0;
-			event != RTE_BPF_VALIDATE_DEBUG_EVENT_END; ++event)
-		if (events & RTE_BIT64(event))
-			rc = rc < 0 ? rc : debug_send_event(debug, event);
+		if (event == RTE_BPF_VALIDATE_DEBUG_EVENT_STEP)
+			rc = rc < 0 ? rc : debug_trigger_breakpoints(debug, pc);
 
-	if (events == 0 || events == RTE_BIT64(
-			RTE_BPF_VALIDATE_DEBUG_EVENT_BRANCH_ENTER))
-		/* Stepping into a real instruction to execute. */
-		rc = rc < 0 ? rc : debug_points_call_back(debug,
-			&debug->breakpoint_lists[pc]);
+		rc = rc < 0 ? rc : debug_send_event(debug, event);
+		events -= RTE_BIT64(event);
+	}
 
-	rc = rc < 0 ? rc : debug_send_event(debug,
-		RTE_BPF_VALIDATE_DEBUG_EVENT_STEP);
+	if (events != 0)
+		/* Received unsupported events. */
+		rc = rc < 0 ? rc : -EINVAL;
 
 	return rc;
 }
@@ -627,8 +651,6 @@ __rte_bpf_validate_debug_evaluate_finish(struct rte_bpf_validate_debug *debug,
 	int result)
 {
 	int rc = 0;
-	uint32_t pc;
-	debug_event_t event;
 
 	if (debug == NULL)
 		return 0;
@@ -641,20 +663,10 @@ __rte_bpf_validate_debug_evaluate_finish(struct rte_bpf_validate_debug *debug,
 	debug->evaluate_finished = true;
 	debug->evaluate_result = result;
 
-	if (result != -ECANCELED) {
-		if (result < 0) {
-			/* Last known pc is the place we failed. */
-			pc = debug->pc;
-			event = RTE_BPF_VALIDATE_DEBUG_EVENT_VALIDATION_FAILURE;
-		} else {
-			/* Show program end, not particular instruction. */
-			pc = debug->bpf_prm->raw.nb_ins;
-			event = RTE_BPF_VALIDATE_DEBUG_EVENT_VALIDATION_SUCCESS;
-		}
-
-		rc = __rte_bpf_validate_debug_evaluate_update(debug, pc,
-			RTE_BIT64(event));
-	}
+	if (result != -ECANCELED)
+		rc = debug_send_event(debug, result < 0 ?
+			RTE_BPF_VALIDATE_DEBUG_EVENT_VALIDATION_FAILURE :
+			RTE_BPF_VALIDATE_DEBUG_EVENT_VALIDATION_SUCCESS);
 
 	debug_evaluate_close(debug);
 
diff --git a/lib/bpf/bpf_validate_debug.h b/lib/bpf/bpf_validate_debug.h
index 2b0c3bb646a7..6d1f1ac99e3e 100644
--- a/lib/bpf/bpf_validate_debug.h
+++ b/lib/bpf/bpf_validate_debug.h
@@ -56,7 +56,6 @@ __rte_bpf_validate_debug_evaluate_start(struct rte_bpf_validate_debug *debug,
  *   Current value of the program counter, or next after last instruction.
  * @param events
  *   Bitmask of events.
- *   Step bit is always implied and should not be set.
  * @return
  *   Non-negative value: evaluation should continue;
  *   -ECANCELED: evaluation should fail without calling this API again;
diff --git a/lib/bpf/rte_bpf_validate_debug.h b/lib/bpf/rte_bpf_validate_debug.h
index f30fa926f10a..b3fedd5428bc 100644
--- a/lib/bpf/rte_bpf_validate_debug.h
+++ b/lib/bpf/rte_bpf_validate_debug.h
@@ -31,25 +31,31 @@ extern "C" {
  * Supported validate events.
  *
  * Valid events begin from 0 and end before `RTE_BPF_VALIDATE_DEBUG_EVENT_END`.
+ *
+ * At any given evaluation step, callbacks are fired in the following order:
+ * - Validation start event;
+ * - Branching and invalid-state events (can be interleaved);
+ * - Instruction breakpoints (before evaluating instruction);
+ * - Step (before evaluating instruction) or validation result (if done) event;
  */
 enum rte_bpf_validate_debug_event {
-	/* Just before every instruction, at branch or validation end. */
+	/* Just before every evaluated instruction. */
 	RTE_BPF_VALIDATE_DEBUG_EVENT_STEP,
 	/* Validator has failed its internal self-checks. */
 	RTE_BPF_VALIDATE_DEBUG_EVENT_INVALID_STATE,
-	/* Start of validation. */
+	/* Start of validation (pc points to first instruction). */
 	RTE_BPF_VALIDATE_DEBUG_EVENT_VALIDATION_START,
-	/* Successful finish of validation. */
+	/* Successful finish (pc undefined). */
 	RTE_BPF_VALIDATE_DEBUG_EVENT_VALIDATION_SUCCESS,
-	/* Finish of validation with error. */
+	/* Finish of validation with error (pc points at error). */
 	RTE_BPF_VALIDATE_DEBUG_EVENT_VALIDATION_FAILURE,
-	/* Beginning of a branch just after the jump. */
+	/* Beginning of a branch evaluation (pc points to branch start). */
 	RTE_BPF_VALIDATE_DEBUG_EVENT_BRANCH_ENTER,
-	/* Pruning branch as verified earlier. */
+	/* Pruning branch as verified earlier (pc points to branch start). */
 	RTE_BPF_VALIDATE_DEBUG_EVENT_BRANCH_PRUNE,
-	/* End of branch verification, after the last verified instruction. */
+	/* End of branch verification (pc points to jump instruction). */
 	RTE_BPF_VALIDATE_DEBUG_EVENT_BRANCH_RETURN,
-	/* Pruning branch as dynamically unreachable. */
+	/* Pruning branch as dynamically unreachable (pc points to branch start). */
 	RTE_BPF_VALIDATE_DEBUG_EVENT_BRANCH_UNREACHABLE,
 	/* Number of valid event values. */
 	RTE_BPF_VALIDATE_DEBUG_EVENT_END,
@@ -208,7 +214,7 @@ rte_bpf_validate_debug_get_last_point(const struct rte_bpf_validate_debug *debug
  * @param debug
  *   Debug instance.
  * @return
- *   Current program counter being validated, or one after last.
+ *   Current program counter being validated.
  *   UINT32_MAX if no program is being validated.
  */
 __rte_experimental
-- 
2.43.0



More information about the dev mailing list