> Add a new DPDK application, dpdk-validate-bpf, for pre-validating eBPF
> programs for compatibility with the lib/bpf execution context.
> 
> The application allows evaluating eBPF programs against the DPDK verifier
> before loading them into a real application. It includes an interactive
> debugging mode to trace state changes per instruction and understand the
> validator's decisions.
> 
> Signed-off-by: Marat Khalili <[email protected]>
> ---
>  MAINTAINERS                            |    2 +
>  app/meson.build                        |    1 +
>  app/validate-bpf/alloc_list.c          |   53 ++
>  app/validate-bpf/args.c                |  263 ++++++
>  app/validate-bpf/debug.c               | 1103 ++++++++++++++++++++++++
>  app/validate-bpf/debug_command.c       |  383 ++++++++
>  app/validate-bpf/debug_command.h       |   65 ++
>  app/validate-bpf/eal_init_args.c       |   57 ++
>  app/validate-bpf/internal.h            |  151 ++++
>  app/validate-bpf/main.c                |   90 ++
>  app/validate-bpf/meson.build           |   13 +
>  app/validate-bpf/parse_decl.c          |  623 +++++++++++++
>  doc/guides/rel_notes/release_26_11.rst |    5 +
>  doc/guides/tools/index.rst             |    1 +
>  doc/guides/tools/validate_bpf.rst      |   97 +++
>  15 files changed, 2907 insertions(+)
>  create mode 100644 app/validate-bpf/alloc_list.c
>  create mode 100644 app/validate-bpf/args.c
>  create mode 100644 app/validate-bpf/debug.c
>  create mode 100644 app/validate-bpf/debug_command.c
>  create mode 100644 app/validate-bpf/debug_command.h
>  create mode 100644 app/validate-bpf/eal_init_args.c
>  create mode 100644 app/validate-bpf/internal.h
>  create mode 100644 app/validate-bpf/main.c
>  create mode 100644 app/validate-bpf/meson.build
>  create mode 100644 app/validate-bpf/parse_decl.c
>  create mode 100644 doc/guides/tools/validate_bpf.rst
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index e99a65d1974d..39cd0a3de6fa 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1923,8 +1923,10 @@ F: lib/bpf/
>  F: examples/bpf/
>  F: app/test/test_bpf*
>  F: app/test/bpf/
> +F: app/validate-bpf/
>  F: app/test-pmd/bpf_cmd.*
>  F: doc/guides/prog_guide/bpf_lib.rst
> +F: doc/guides/tools/validate_bpf.rst
> 
>  Graph
>  M: Jerin Jacob <[email protected]>
> diff --git a/app/meson.build b/app/meson.build
> index 1798db3ae43f..9b59ae483056 100644
> --- a/app/meson.build
> +++ b/app/meson.build
> @@ -33,6 +33,7 @@ apps = [
>          'test-regex',
>          'test-sad',
>          'test-security-perf',
> +        'validate-bpf',
>  ]
> 
>  if get_option('tests')
> diff --git a/app/validate-bpf/alloc_list.c b/app/validate-bpf/alloc_list.c
> new file mode 100644
> index 000000000000..2f4d18ad3df6
> --- /dev/null
> +++ b/app/validate-bpf/alloc_list.c
> @@ -0,0 +1,53 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2025 Huawei Technologies Co., Ltd
> + */
> +
> +#include "internal.h"
> +#include <stdlib.h>
> +
> +
> +/* Needs to be a power of two. */
> +#define START_CAPACITY (1u << 3)
> +
> +size_t
> +alloc_list_append(struct alloc_list *alloc_list, void *ptr)
> +{
> +     if (alloc_list->count == 0) {
> +             RTE_ASSERT(alloc_list->ptrs == NULL);
> +             alloc_list->ptrs = malloc(
> +                     sizeof(alloc_list->ptrs[0]) * START_CAPACITY);
> +             RTE_VERIFY(alloc_list->ptrs != NULL);
> +     } else if (alloc_list->count >= START_CAPACITY &&
> +                     /* Power of two detection */
> +                     (alloc_list->count & (alloc_list->count - 1)) == 0) {
> +             /*
> +              * We allocate in powers of two, and current count is one of
> +              * these powers, so need to reallocate to larger capacity.
> +              */
> +             RTE_ASSERT(alloc_list->ptrs != NULL);
> +             const size_t new_capacity = alloc_list->count * 2;
> +             alloc_list->ptrs = realloc(alloc_list->ptrs,
> +                     sizeof(alloc_list->ptrs[0]) * new_capacity);
> +             RTE_VERIFY(alloc_list->ptrs != NULL);
> +     }
> +     alloc_list->ptrs[alloc_list->count] = ptr;
> +     return alloc_list->count++;
> +}
> +
> +void alloc_list_replace(struct alloc_list *alloc_list, size_t index, void 
> *ptr)
> +{
> +     RTE_ASSERT(index < alloc_list->count);
> +     alloc_list->ptrs[index] = ptr;
> +}
> +
> +void alloc_list_free_all(struct alloc_list *alloc_list)
> +{
> +     /* Copy and clear fields first in case alloc_list itself gets freed. */
> +     size_t count = alloc_list->count;
> +     void ** const ptrs = alloc_list->ptrs;
> +     *alloc_list = (struct alloc_list){};
> +
> +     while (count != 0)
> +             free(ptrs[--count]);
> +     free(ptrs);
> +}
> diff --git a/app/validate-bpf/args.c b/app/validate-bpf/args.c
> new file mode 100644
> index 000000000000..cd7cbd694a3c
> --- /dev/null
> +++ b/app/validate-bpf/args.c
> @@ -0,0 +1,263 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2025 Huawei Technologies Co., Ltd
> + */
> +
> +#include "internal.h"
> +
> +#include <ctype.h>
> +#include <getopt.h>
> +#include <stdlib.h>
> +
> +#include <rte_errno.h>
> +
> +
> +/* Values to be used in getopt_long option.val. */
> +enum app_args {
> +     ARG_UNRECOGNIZED = '?',
> +     ARG_AUTO = 0,   /* Value set by getopt_long when flag is non-NULL. */
> +     ARG_HELP,       /* Keep this one in the beginning for UX reasons. */
> +     ARG_DEBUG,
> +     ARG_MBUF_BUF_SIZE,
> +     ARG_NO_PROG_ARGS,
> +     ARG_PROG_ARG,
> +     ARG_SECTION,
> +     ARG_XSYM,
> +};
> +
> +/* Program options. */
> +static struct option OPTIONS[] = {

Here and everywhere:
By DPDK coding convention variable names are in lowercase:
https://doc.dpdk.org/guides-25.07/contributing/coding_style.html

> +     {
> +             .name = "help",
> +             .val = ARG_HELP,
> +     },
> +     {
> +             .name = "debug",
> +             .val = ARG_DEBUG,
> +     },
> +     {
> +             .name = "mbuf-buf-size",
> +             .has_arg = required_argument,
> +             .val = ARG_MBUF_BUF_SIZE,
> +     },
> +     {
> +             .name = "no-prog-arg",
> +             .val = ARG_NO_PROG_ARGS,
> +     },
> +     {
> +             .name = "no-prog-args",
> +             .val = ARG_NO_PROG_ARGS,
> +     },
> +     {
> +             .name = "prog-arg",
> +             .has_arg = required_argument,
> +             .val = ARG_PROG_ARG,
> +     },
> +     {
> +             .name = "section",
> +             .has_arg = required_argument,
> +             .val = ARG_SECTION,
> +     },
> +     {
> +             .name = "xsym",
> +             .has_arg = required_argument,
> +             .val = ARG_XSYM,
> +     },
> +     { /* terminating zero record */ }
> +};
> +
> +/* Default args value. */
> +static const struct args ARGS_DEFAULT = {
> +     .bpf_prm = {
> +             .sz = sizeof(struct rte_bpf_prm_ex),
> +             .origin = RTE_BPF_ORIGIN_ELF_FILE,
> +             .elf_file.section = ".text",
> +     },
> +     .mbuf_buf_size = RTE_MBUF_DEFAULT_BUF_SIZE,
> +};
> +
> +/* Default --prog-arg argument value. */
> +static const char * const PROG_ARG_DEFAULT = "struct rte_mbuf *";
> +
> +
> +void
> +print_usage(const char *program_name)
> +{
> +     static const char *const options[][2] = {
> +             { "--help", "Display help and exit." },
> +             { "--debug", "Enable interactive debug mode." },
> +             { "--mbuf-buf-size=MBUF_BUF_SIZE", "Size of the mbuf data buffer
> (in bytes)." },
> +             { "--prog-arg=TYPE", "Expected type of the next BPF program
> argument (up to 5)." },
> +             { "--no-prog-args", "BPF program does not take any arguments." 
> },
> +             { "--section=SECTION", "ELF section name in the BPF object file 
> to
> load." },
> +             { "--xsym='TYPE NAME[(TYPE, ...)]'", "External symbol BPF 
> program
> can access." },
> +     };
> +
> +     printf("USAGE: %s [OPTIONS]... BPF_PATH\n", program_name);
> +     printf("OPTIONS:\n");
> +     for (int oi = 0; oi != RTE_DIM(options); ++oi)
> +             printf("\t%-31s %s\n", options[oi][0], options[oi][1]);
> +}
> +
> +void
> +print_defaults(void)
> +{
> +     printf("DEFAULTS:"
> +                     " --mbuf-buf-size=%#zx"
> +                     " --prog-arg='%s'"
> +                     " --section='%s'\n",
> +             (size_t)RTE_MBUF_DEFAULT_BUF_SIZE,
> +             PROG_ARG_DEFAULT,
> +             ARGS_DEFAULT.bpf_prm.elf_file.section);
> +}
> +
> +static int
> +parse_size(size_t *result, const char *text)
> +{
> +     char *parse_end = NULL;
> +
> +     errno = 0;
> +     const uintmax_t strtoumax_result = strtoumax(text, &parse_end, 0);

Curious, why not just stroul()?

> +     if (errno != 0 || *parse_end != '\0' || strtoumax_result == 0 ||
> +                     strtoumax_result > SIZE_MAX)
> +             return -1;
> +
> +     *result = strtoumax_result;
> +     return 0;
> +}
> +
> +struct args *
> +args_parse(int argc, char *argv[])
> +{
> +     int val;
> +     size_t xsym_alloc_index;
> +     struct rte_bpf_xsym *xsym = NULL;
> +     const char * const program_name = argv[0];
> +
> +     /* Allocate args and set aliases for some of its members. */
> +     struct args * const args = malloc(sizeof(*args));
> +     RTE_VERIFY(args != NULL);
> +     struct alloc_list * const alloc_list = &args->_alloc_list;
> +     struct rte_bpf_prm_ex * const bpf_prm = &args->bpf_prm;
> +
> +     /* Set default values. */
> +     *args = ARGS_DEFAULT;
> +     RTE_VERIFY(parse_arg(&bpf_prm->prog_arg[bpf_prm->nb_prog_arg++],
> +             PROG_ARG_DEFAULT) >= 0);
> +     bool default_prog_args = true;
> +
> +     /* Reserve space for xsym in alloc_list. */
> +     xsym_alloc_index = alloc_list_append(alloc_list, xsym);
> +
> +     while ((val = getopt_long(argc, argv, "", OPTIONS, NULL)) != EOF) {
> +             int rc = 0;
> +             switch (val) {
> +             case ARG_AUTO:
> +                     /* getopt_long made the assignment, nothing to do */
> +                     break;
> +             case ARG_HELP:
> +                     args->show_help = true;
> +                     break;
> +             case ARG_DEBUG:
> +                     if (bpf_prm->debug == NULL)
> +                             bpf_prm->debug = debug_create();
> +                     if (bpf_prm->debug == NULL) {
> +                             rc = -rte_errno;
> +                             fprintf(stderr,
> +                                     "%s: error %d creating debug session\n",
> +                                     program_name, -rc);
> +                     }
> +                     break;
> +             case ARG_MBUF_BUF_SIZE:
> +                     rc = parse_size(&args->mbuf_buf_size, optarg);
> +                     if (rc < 0)
> +                             fprintf(stderr,
> +                                     "%s: invalid mbuf buf size '%s'\n",
> +                                     program_name, optarg);
> +                     break;
> +             case ARG_NO_PROG_ARGS:
> +                     bpf_prm->nb_prog_arg = 0;
> +                     default_prog_args = false;
> +                     break;
> +             case ARG_PROG_ARG:
> +                     if (default_prog_args) {
> +                             bpf_prm->nb_prog_arg = 0;
> +                             default_prog_args = false;
> +                     }
> +
> +                     if (bpf_prm->nb_prog_arg == RTE_DIM(bpf_prm-
> >prog_arg)) {
> +                             fprintf(stderr,
> +                                     "%s: at most %d program arguments
> allowed\n",
> +                                     program_name,
> +                                     (int)RTE_DIM(bpf_prm->prog_arg));
> +                             rc = -EINVAL;
> +                             break;
> +                     }
> +
> +                     rc = parse_arg(&bpf_prm->prog_arg[bpf_prm-
> >nb_prog_arg++],
> +                             optarg);
> +                     if (rc < 0)
> +                             fprintf(stderr,
> +                                     "%s: unrecognized prog arg '%s'\n",
> +                                     program_name, optarg);
> +
> +                     break;
> +             case ARG_SECTION:
> +                     bpf_prm->elf_file.section = optarg;
> +                     break;
> +             case ARG_XSYM:
> +                     bpf_prm->xsym = xsym = realloc(xsym,
> +                             sizeof(xsym[0]) * (bpf_prm->nb_xsym + 1));
> +                     RTE_VERIFY(xsym != NULL);
> +                     alloc_list_replace(alloc_list, xsym_alloc_index, xsym);
> +                     rc = parse_xsym(&xsym[bpf_prm->nb_xsym++], optarg,
> +                                     alloc_list);
> +                     if (rc < 0)
> +                             fprintf(stderr, "%s: invalid xsym '%s'\n",
> +                                     program_name, optarg);
> +                     break;
> +             case ARG_UNRECOGNIZED:
> +                     args_destroy(args);
> +                     return NULL;
> +             default:
> +                     rte_panic("Unexpected getopt_long return value %d\n",
> +                             val);
> +             }
> +             if (rc < 0) {
> +                     args_destroy(args);
> +                     return NULL;
> +             }
> +     }
> +
> +     /* Set buf_size to the value specified in command line arguments. */
> +     adjust_arg_buf_size(bpf_prm->prog_arg, args->mbuf_buf_size);
> +     for (size_t xsymi = 0; xsymi != bpf_prm->nb_xsym; ++xsymi)
> +             adjust_xsym_buf_size(&xsym[xsymi], args->mbuf_buf_size);
> +
> +     /* getopt_long moves all non-options to the end, starting at optind. */
> +     const int nb_bpf_path = argc - optind;
> +     switch (nb_bpf_path) {
> +     case 0:
> +             break;
> +     case 1:
> +             bpf_prm->elf_file.path = argv[optind];
> +             break;
> +     default:
> +             fprintf(stderr, "%s: too many positional arguments\n",
> +                     program_name);
> +             args_destroy(args);
> +             return NULL;
> +     }
> +
> +     return args;
> +}
> +
> +void
> +args_destroy(struct args *args)
> +{
> +     if (args == NULL)
> +             return;
> +     if (args->bpf_prm.debug != NULL)
> +             debug_destroy(args->bpf_prm.debug);
> +     alloc_list_free_all(&args->_alloc_list);
> +     free(args);
> +}
> diff --git a/app/validate-bpf/debug.c b/app/validate-bpf/debug.c
> new file mode 100644
> index 000000000000..ac085b53e3bd
> --- /dev/null
> +++ b/app/validate-bpf/debug.c
> @@ -0,0 +1,1103 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2025 Huawei Technologies Co., Ltd
> + */
> +
> +#include "debug_command.h"
> +#include "internal.h"
> +
> +#include <rte_bpf_validate_debug.h>
> +#include <rte_errno.h>
> +
> +#include <stdlib.h>
> +
> +
> +/* Write single line to the user, currently just to stdout. */
> +#define PRINTLN(fmt, ...) do { \
> +     RTE_LOG_CHECK_NO_NEWLINE(fmt); \
> +     printf(fmt "\n", ##__VA_ARGS__); \
> +} while (0)
> +
> +#define PROMPT "(validate) "
> +
> +#define INITIAL_CAPACITY 8u
> +
> +static const char *const event_names[] = {
> +     [RTE_BPF_VALIDATE_DEBUG_EVENT_INVALID_STATE] = "invalid-state",
> +     [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",
> +};
> +
> +static const char *const register_names[] = {
> +     [EBPF_REG_0] = "r0",
> +     [EBPF_REG_1] = "r1",
> +     [EBPF_REG_2] = "r2",
> +     [EBPF_REG_3] = "r3",
> +     [EBPF_REG_4] = "r4",
> +     [EBPF_REG_5] = "r5",
> +     [EBPF_REG_6] = "r6",
> +     [EBPF_REG_7] = "r7",
> +     [EBPF_REG_8] = "r8",
> +     [EBPF_REG_9] = "r9",
> +     [EBPF_REG_10] = "r10",
> +};
> +
> +static const char *const comparison_operator_names[] = {
> +     [BPF_JEQ] = "==",
> +     [BPF_JGT] = ">",
> +     [BPF_JGE] = ">=",
> +     [EBPF_JNE] = "!=",
> +     [EBPF_JSGT] = "s>",
> +     [EBPF_JSGE] = "s>=",
> +     [EBPF_JLT] = "<",
> +     [EBPF_JLE] = "<=",
> +     [EBPF_JSLT] = "s<",
> +     [EBPF_JSLE] = "s<=",
> +};
> +
> +enum point_type {
> +     POINT_TYPE_BREAK,
> +     POINT_TYPE_CATCH,
> +};
> +
> +/* Local representation of points with additional info for UI. */
> +struct point_info {
> +     struct rte_bpf_validate_debug_point *point;
> +     enum point_type type;
> +     union {
> +             uint32_t pc;
> +             enum rte_bpf_validate_debug_event event;
> +     };
> +};
> +
> +/* Dynamically growing list of point infos. */
> +struct point_infos {
> +     struct point_info *elements;
> +     uint32_t length;
> +     uint32_t capacity;
> +};
> +
> +/* Information about a single conditional or unconditional jump in code 
> path. */
> +struct branch_info {
> +     uint32_t jump_pc;
> +     uint32_t target_pc;
> +     bool is_conditional;
> +};
> +
> +/* Dynamically growing stack to track code path. */
> +struct branch_stack {
> +     struct branch_info *branches;
> +     uint32_t length;
> +     uint32_t capacity;
> +};
> +
> +/* Flags telling if we should validate again, stopping or not at start. */
> +static bool validate_again;
> +
> +/* List of point infos; element index is its ID for UI. */
> +static struct point_infos point_infos;
> +
> +/* Catchpoint invisible to the user used for step-by-step validation. */
> +static struct rte_bpf_validate_debug_point *step_point;
> +
> +/* Tracking entered branches. */
> +static struct branch_stack branch_stack;
> +static uint32_t pending_jump_pc = UINT32_MAX;
> +static struct rte_bpf_validate_debug_point *jump_always_step_point;
> +
> +static int
> +step_cb(struct rte_bpf_validate_debug *debug, void *ctx);
> +static int
> +point_cb(struct rte_bpf_validate_debug *debug, void *ctx);
> +
> +/* Find index of the string in the list. */
> +static int
> +find_name(const char *name, const char *const *names, int nb_names)
> +{
> +     if (nb_names < 0)
> +             return -EINVAL;
> +
> +     for (int index = 0; index != nb_names; ++index)
> +             if (names[index] != NULL && strcmp(names[index], name) == 0)
> +                     return index;
> +
> +     return -ENOENT;
> +}
> +
> +/* Convert event name to its enum value. */
> +static enum rte_bpf_validate_debug_event
> +parse_event_name(const char *name)
> +{
> +     const int rc = find_name(name, event_names, RTE_DIM(event_names));
> +     if (rc < 0)
> +             PRINTLN("Error: invalid event name.");
> +
> +     return rc;
> +}
> +
> +/* Convert comparison operator name to opcode. */
> +static int
> +parse_comparison_operator(const char *name)
> +{
> +     const int rc = find_name(name, comparison_operator_names,
> +             RTE_DIM(comparison_operator_names));
> +     if (rc < 0)
> +             PRINTLN("Error: invalid comparison operator name.");
> +
> +     return rc;
> +}
> +
> +/* Convert register name to its number. */
> +static int
> +parse_register(const char *name)
> +{
> +     const int rc = find_name(name, register_names, RTE_DIM(register_names));
> +     if (rc < 0)
> +             PRINTLN("Error: invalid register name.");
> +
> +     return rc;
> +}
> +
> +/* Free local list of point infos (do not destroy points). */
> +static void
> +point_infos_free(void)
> +{
> +     free(point_infos.elements);
> +     point_infos = (struct point_infos){};
> +}
> +
> +/* Free local branch stack. */
> +static void
> +branch_stack_free(void)
> +{
> +     free(branch_stack.branches);
> +     branch_stack = (struct branch_stack){};
> +}
> +
> +/* Return existing element from the list of point infos. */
> +static struct point_info *
> +point_infos_at(uint32_t point_number)
> +{
> +     RTE_ASSERT(point_number < point_infos.length);
> +     RTE_ASSERT(point_infos.elements[point_number].point != NULL);
> +     return &point_infos.elements[point_number];
> +}
> +
> +/* Print existing element from the list of point infos. */
> +static void
> +point_infos_print_at(uint32_t point_number)
> +{
> +     const struct point_info *const point_info = 
> point_infos_at(point_number);
> +
> +     switch (point_info->type) {
> +     case POINT_TYPE_BREAK:
> +             PRINTLN("Breakpoint %d at %d.", point_number,
> +                     point_info->pc);
> +             break;
> +     case POINT_TYPE_CATCH:
> +             PRINTLN("Catchpoint %d on %s.", point_number,
> +                     event_names[point_info->event]);
> +             break;
> +     default:
> +             PRINTLN("Point %d of unknown type", point_number);
> +             break;
> +     }
> +}
> +
> +/* Print all point infos. */
> +static int
> +point_infos_print_all(void)
> +{
> +     uint32_t nb_printed = 0;
> +     for (uint32_t pn = 0; pn != point_infos.length; ++pn) {
> +             const struct point_info *const point_info =
> +                     &point_infos.elements[pn];
> +             if (point_info->point != NULL) {
> +                     point_infos_print_at(pn);
> +                     ++nb_printed;
> +             }
> +     }
> +
> +     if (nb_printed == 0) {
> +             PRINTLN("No breakpoints or catchpoints set.");
> +             return -ENOENT;
> +     }
> +
> +     return nb_printed;
> +}
> +
> +/* Allocate space for a new element in the list of point infos. */
> +static uint32_t
> +point_infos_append(void)
> +{
> +     if (point_infos.length == point_infos.capacity) {
> +             /* Set to initial capacity or double previous one. */
> +             point_infos.capacity = RTE_MAX(INITIAL_CAPACITY,
> +                     point_infos.capacity * 2);
> +             point_infos.elements = realloc(point_infos.elements,
> +                     point_infos.capacity * sizeof(point_infos.elements[0]));
> +             RTE_VERIFY(point_infos.elements != NULL);
> +     }
> +     return point_infos.length++;
> +}
> +
> +/* Allocate space for a new element in the branch stack. */
> +static void
> +branch_stack_append(const struct branch_info *branch)
> +{
> +     if (branch_stack.length == branch_stack.capacity) {
> +             /* Set to initial capacity or double previous one. */
> +             branch_stack.capacity = RTE_MAX(INITIAL_CAPACITY,
> +                     branch_stack.capacity * 2);
> +             branch_stack.branches = realloc(branch_stack.branches,
> +                     branch_stack.capacity * 
> sizeof(branch_stack.branches[0]));
> +             RTE_VERIFY(branch_stack.branches != NULL);
> +     }
> +     branch_stack.branches[branch_stack.length++] = *branch;
> +}
> +
> +/* Destroy existing element from the list of point infos, printing it first. 
> */
> +static void
> +point_infos_destroy_existing(uint32_t point_number)
> +{
> +     point_infos_print_at(point_number);
> +
> +     struct point_info *const point_info = point_infos_at(point_number);
> +     rte_bpf_validate_debug_point_destroy(point_info->point);
> +     *point_info = (struct point_info){};
> +}
> +
> +/* Destroy point with specified number if it exists. */
> +static int
> +point_infos_destroy_at(uint32_t point_number)
> +{
> +     if (point_number >= point_infos.length ||
> +                     point_infos.elements[point_number].point == NULL) {
> +             PRINTLN("No breakpoint number %d.", point_number);
> +             return -ENOENT;
> +     }
> +
> +     point_infos_destroy_existing(point_number);
> +     return 1;
> +}
> +
> +/* Destroy all point infos. */
> +static int64_t
> +point_infos_destroy_all(void)
> +{
> +     uint32_t nb_destroyed = 0;
> +     for (uint32_t pn = 0; pn < point_infos.length; ++pn) {
> +             const struct point_info *const point_info =
> +                     &point_infos.elements[pn];
> +             if (point_info->point != NULL) {
> +                     point_infos_destroy_existing(pn);
> +                     ++nb_destroyed;
> +             }
> +     }
> +
> +     if (nb_destroyed == 0) {
> +             PRINTLN("No breakpoints or catchpoints set.");
> +             return -ENOENT;
> +     }
> +
> +     return nb_destroyed;
Curious if function always returns uint32_t, why make it to return 'int64_t'?
For errors? But the only error is -ENOENT when nb_destroyed == 0.
Same question for similar functions below. 
> +}
> +
> +/* Destroy all breakpoints at specified location. */
> +static int64_t
> +point_infos_destroy_breakpoints(uint32_t pc)
> +{
> +     uint32_t nb_destroyed = 0;
> +     for (uint32_t pn = 0; pn < point_infos.length; ++pn) {
> +             const struct point_info *const point_info =
> +                     &point_infos.elements[pn];
> +             if (point_info->point != NULL &&
> +                             point_info->type == POINT_TYPE_BREAK &&
> +                             point_info->pc == pc) {
> +                     point_infos_destroy_existing(pn);
> +                     ++nb_destroyed;
> +             }
> +     }
> +
> +     if (nb_destroyed == 0) {
> +             PRINTLN("No breakpoint at %u.", pc);
> +             return -ENOENT;
> +     }
> +
> +     return nb_destroyed;
> +}
> +
> +/* Destroy all catchpoints for specified event. */
> +static int64_t
> +point_infos_destroy_catchpoints(int event)
> +{
> +     if (event < 0)
> +             /* Error was already printed by parse_event_name. */
> +             return event;
> +
> +     uint32_t nb_destroyed = 0;
> +     for (uint32_t pn = 0; pn < point_infos.length; ++pn) {
> +             const struct point_info *const point_info =
> +                     &point_infos.elements[pn];
> +             if (point_info->point != NULL &&
> +                             point_info->type == POINT_TYPE_CATCH &&
> +                             (int)point_info->event == event) {
> +                     point_infos_destroy_existing(pn);
> +                     ++nb_destroyed;
> +             }
> +     }
> +
> +     if (nb_destroyed == 0) {
> +             PRINTLN("No catchpoint on %s.", event_names[event]);
> +             return -ENOENT;
> +     }
> +
> +     return nb_destroyed;
> +}
> +
> +/* Create new breakpoint at specified location. */
> +static int
> +add_breakpoint(struct rte_bpf_validate_debug *debug, uint32_t nb_ins, 
> uint32_t
> pc)
> +{
> +     const uint32_t point_number = point_infos_append();
> +
> +     if (pc >= nb_ins) {
> +             PRINTLN("Error: program only has %u instructions.", nb_ins);
> +             return -ENOENT;
> +     }
> +
> +     struct rte_bpf_validate_debug_point *const point =
> +             rte_bpf_validate_debug_break(debug, pc,
> +                     &(struct rte_bpf_validate_debug_callback){
> +                             .fn = point_cb,
> +                             .ctx = (void *)(uintptr_t)point_number,
> +                     });
> +     if (point == NULL) {
> +             PRINTLN("Library error %d.", rte_errno);
> +             return -rte_errno;
> +     }
> +
> +     point_infos.elements[point_number] = (struct point_info){
> +                     .point = point,
> +                     .type = POINT_TYPE_BREAK,
> +                     .pc = pc,
> +             };
> +     point_infos_print_at(point_number);
> +     return 0;
> +}
> +
> +/* Create new catchpoint at specified location. */
> +static int
> +add_catchpoint(struct rte_bpf_validate_debug *debug, int event)
> +{
> +     if (event < 0)
> +             /* Error was already printed by parse_event_name. */
> +             return event;
> +
> +     const uint32_t point_number = point_infos_append();
> +     struct rte_bpf_validate_debug_point *const point =
> +             rte_bpf_validate_debug_catch(debug, event,
> +                     &(struct rte_bpf_validate_debug_callback){
> +                             .fn = point_cb,
> +                             .ctx = (void *)(uintptr_t)point_number,
> +                     });
> +     if (point == NULL) {
> +             PRINTLN("Library error %d.", rte_errno);
> +             return -rte_errno;
> +     }
> +
> +     point_infos.elements[point_number] = (struct point_info){
> +                     .point = point,
> +                     .type = POINT_TYPE_CATCH,
> +                     .event = event,
> +             };
> +     point_infos_print_at(point_number);
> +     return 0;
> +}
> +
> +static bool
> +is_step_enabled(void)
> +{
> +     return step_point != NULL;
> +}
> +
> +/* Enable step-by-step validation: make sure catchpoint is set on step 
> event. */
> +static int
> +enable_step(struct rte_bpf_validate_debug *debug)
> +{
> +     if (is_step_enabled())
> +             return 0;
> +
> +     step_point = rte_bpf_validate_debug_catch(debug,
> +             RTE_BPF_VALIDATE_DEBUG_EVENT_STEP,
> +             &(struct rte_bpf_validate_debug_callback){ step_cb });
> +     if (step_point == NULL) {
> +             PRINTLN("Library error %d.", rte_errno);
> +             return -rte_errno;
> +     }
> +
> +     return 0;
> +}
> +
> +/* Disable step-by-step validation: destroy catchpoint on step event if any. 
> */
> +static void
> +disable_step(void)
> +{
> +     rte_bpf_validate_debug_point_destroy(step_point);
> +     step_point = NULL;
> +}
> +
> +/* Format and print information about specified frame offset. */
> +static int
> +print_frame_offset(struct rte_bpf_validate_debug *debug, int32_t offset)
> +{
> +     char *info;
> +     int info_size, rc;
> +
> +     if (offset >= 0 || offset % sizeof(uint64_t) != 0) {
> +             PRINTLN("Invalid frame offset, must be a negative multiple of
> %zu.",
> +                     sizeof(uint64_t));
> +             return -EINVAL;
> +     }
> +
> +     rc = rte_bpf_validate_debug_format_frame_info(debug, NULL, 0, offset);
> +     if (rc == -ERANGE) {
> +             PRINTLN("Offset is out of frame range.");
> +             return rc;
> +     }
> +     if (rc < 0) {
> +             PRINTLN("Error %d printing information.", -rc);
> +             return rc;
> +     }
> +
> +     info_size = rc + 1;
> +     info = malloc(info_size);
> +     if (info == NULL)
> +             return -ENOMEM;
> +
> +     rc = rte_bpf_validate_debug_format_frame_info(debug, info, info_size,
> +             offset);
> +     if (rc + 1 != info_size) {
> +             if (rc >= 0) {
> +                     PRINTLN("Expect format return value %d, got %d.",
> +                             info_size, rc);
> +                     rc = -EINVAL;
> +             } else
> +                     PRINTLN("Error %d printing information.", -rc);
> +             free(info);
> +             return rc;
> +     }
> +
> +     printf("%5jd: \t%s\n", (intmax_t)offset, info);

Again, what for intmax_t here, if offset is just 32bit int?

> +     free(info);
> +     return 0;
> +}
> +
> +/* Format and print informatiion about the frame. */
> +static int
> +print_frame(struct rte_bpf_validate_debug *debug)
> +{
> +     int32_t frame_size;
> +     int rc;
> +
> +     frame_size = rte_bpf_validate_debug_get_frame_size(debug);
> +     if (frame_size < 0) {
> +             PRINTLN("Error %d getting frame size.", -frame_size);
> +             return frame_size;
> +     }
> +
> +     for (int32_t frame_offset = 0;;) {

That's an unusal loop construction...
Why not something more common, obvious and easy to read?
i.e:
for(ofs = sizeof(uint64_t); ofs < frame_size;  ofs += sizeof(uint64_t)) {
   rc = print_frame_offset(debug, -ofs);
   if (rc < 0) return rc;
}

> +             frame_offset -= sizeof(uint64_t);
> +             if (frame_offset < -frame_size)
> +                     break;
> +
> +             rc = print_frame_offset(debug, frame_offset);
> +             if (rc < 0)
> +                     return rc;
> +     }
> +
> +     return 0;
> +}
> +
> +/* Format and print informatiion about specified register. */
> +static int
> +print_register(struct rte_bpf_validate_debug *debug, int reg)
> +{
> +     char *info;
> +     int info_size, rc;
> +
> +     if (reg < 0)
> +             /* Error was already printed by parse_register. */
> +             return reg;
> +
> +     rc = rte_bpf_validate_debug_format_register_info(debug, NULL, 0, reg);
> +     if (rc < 0) {
> +             PRINTLN("Error %d printing information.", -rc);
> +             return rc;
> +     }
> +
> +     info_size = rc + 1;
> +     info = malloc(info_size);
> +     if (info == NULL)
> +             return -ENOMEM;

In some places you panic (RTE_VERIFY) when malloc() fails, in others return an 
error?
Why? Any strategy behind?
 
> +
> +     rc = rte_bpf_validate_debug_format_register_info(debug, info, info_size,
> +             reg);
> +     if (rc + 1 != info_size) {
> +             if (rc >= 0) {
> +                     PRINTLN("Expect format return value %d, got %d.",
> +                             info_size, rc);
> +                     rc = -EINVAL;
> +             } else
> +                     PRINTLN("Error %d printing information.", -rc);
> +             free(info);
> +             return rc;
> +     }
> +
> +     printf("%5s: \t%s\n", register_names[reg], info);
> +     free(info);
> +     return 0;
> +}
> +
> +/* Format and print informatiion about all registers. */
> +static int
> +print_registers(struct rte_bpf_validate_debug *debug)
> +{
> +     int rc = 0;
> +
> +     for (int reg = 0; reg != EBPF_REG_NUM; ++reg)
> +             rc = rc < 0 ? rc : print_register(debug, reg);
> +
> +     return rc;
> +}
> +
> +/* List one eBPF program instruction. */
> +static int
> +list_one(const struct ebpf_insn *ins, uint32_t nb_ins, uint32_t offset,
> +     uint32_t pc, const char *comment)
> +{
> +     char hexadecimal[256], disassembly[256];
> +
> +     if (offset >= nb_ins) {
> +             PRINTLN("Error: program only has %u instructions.", nb_ins);
> +             return -EINVAL;
> +     }
> +
> +     ins += offset;
> +
> +     if (offset == nb_ins - 1 && rte_bpf_insn_is_wide(ins)) {
> +             PRINTLN("Error: truncated last instruction.");
> +             return -EINVAL;
> +     }
> +
> +     rte_bpf_format(hexadecimal, sizeof(hexadecimal), ins, 0,
> +             RTE_BPF_FORMAT_FLAG_HEXADECIMAL |
> +             RTE_BPF_FORMAT_FLAG_NEVER_WIDE);
> +     rte_bpf_format(disassembly, sizeof(disassembly), ins, offset,
> +             RTE_BPF_FORMAT_FLAG_DISASSEMBLY |
> +             RTE_BPF_FORMAT_FLAG_ABSOLUTE_JUMPS);
> +
> +     if (comment == NULL)
> +             comment = "";
> +     PRINTLN("%2s %10u: \t%s \t%s%s%s",
> +             offset == pc ? "=>" : "", offset, hexadecimal, disassembly,
> +             comment[0] != '\0' ? " \t; " : "", comment);
> +
> +     if (rte_bpf_insn_is_wide(ins)) {
> +             rte_bpf_format(hexadecimal, sizeof(hexadecimal), ins + 1, 0,
> +                     RTE_BPF_FORMAT_FLAG_HEXADECIMAL |
> +                     RTE_BPF_FORMAT_FLAG_NEVER_WIDE);
> +             PRINTLN("%15s\t%s", "", hexadecimal);
> +     }
> +
> +     return 0;
> +}
> +
> +/* List specified range of eBPF program instructions, updating start offset. 
> */
> +static int
> +list(const struct ebpf_insn *ins, uint32_t nb_ins, uint32_t *offset,
> +     uint32_t count, uint32_t pc)
> +{
> +     uint32_t local_offset = 0;
> +     if (offset == NULL)
> +             offset = &local_offset;
> +
> +     if (*offset > nb_ins) {
> +             PRINTLN("Error: program only has %u instructions.", nb_ins);
> +             return -EINVAL;
> +     }
> +
> +     const uint32_t end =
> +             /* Calculate end in a way preventing overflow: */
> +             *offset + RTE_MIN(nb_ins - *offset, count);
> +     while (*offset < end) {
> +             const int rc = list_one(ins, nb_ins, *offset, pc, NULL);
> +             if (rc < 0)
> +                     return rc;
> +
> +             *offset += 1 + rte_bpf_insn_is_wide(&ins[*offset]);
> +     }
> +
> +     return 0;
> +}
> +
> +/* Print if specified conditional jump _may_ be executed. */
> +static int
> +print_if_may(struct rte_bpf_validate_debug *debug, const struct ebpf_insn
> *jump,
> +     uint64_t imm64)
> +{
> +     const int result = rte_bpf_validate_debug_may_jump(debug, jump,
> imm64);
> +
> +     switch (result) {
> +     case 0:
> +     case RTE_BPF_VALIDATE_DEBUG_MAY_BE_FALSE:
> +             PRINTLN("NO");
> +             break;
> +     case RTE_BPF_VALIDATE_DEBUG_MAY_BE_TRUE:
> +     case RTE_BPF_VALIDATE_DEBUG_MAY_BE_FALSE |
> RTE_BPF_VALIDATE_DEBUG_MAY_BE_TRUE:
> +             PRINTLN("YES");
> +             break;
> +     default:
> +             PRINTLN("Error %d getting result.", -result);
> +             break;
> +     }
> +
> +     return result;
> +}
> +
> +/* Print if specified condition with literal right hand side _may_ be true. 
> */
> +static int
> +print_if_may_literal_rhs(struct rte_bpf_validate_debug *debug,
> +     const struct debug_command_comparison *comparison)
> +{
> +     const int lhs = parse_register(comparison->lhs);
> +     const int op = parse_comparison_operator(comparison->op);
> +     const int64_t rhs = comparison->literal_rhs;
> +
> +     if (lhs < 0 || op < 0)
> +             /* Error was already printed by parse function. */
> +             return lhs < 0 ? lhs : op;
> +
> +     return print_if_may(debug, &(struct ebpf_insn){
> +             .code = BPF_JMP | op | BPF_K,
> +             .dst_reg = lhs,
> +     }, /* imm64 = */ rhs);
> +}
> +
> +/* Print if specified condition with register right hand side _may_ be true. 
> */
> +static int
> +print_if_may_register_rhs(struct rte_bpf_validate_debug *debug,
> +     const struct debug_command_comparison *comparison)
> +{
> +     const int lhs = parse_register(comparison->lhs);
> +     const int op = parse_comparison_operator(comparison->op);
> +     const int rhs = parse_register(comparison->register_rhs);
> +
> +     if (lhs < 0 || op < 0 || rhs < 0)
> +             /* Error was already printed by parse function. */
> +             return lhs < 0 ? lhs : op < 0 ? op : rhs;
> +
> +     return print_if_may(debug, &(struct ebpf_insn){
> +             .code = BPF_JMP | op | BPF_X,
> +             .dst_reg = lhs,
> +             .src_reg = rhs,
> +     }, /* imm64 = */ 0);
> +}
> +
> +/* Return 1 on validation success, 0 on failure, -EAGAIN if still running. */
> +static int
> +get_validation_success(struct rte_bpf_validate_debug *debug)
> +{
> +     int validation_result, rc;
> +
> +     rc = rte_bpf_validate_debug_get_validation_result(debug,
> +             &validation_result);
> +     return rc < 0 ? rc : (validation_result >= 0);
> +}
> +
> +static void
> +print_status(const struct ebpf_insn *ins, uint32_t nb_ins,
> +     int validation_success, uint32_t pc)
> +{
> +     if (validation_success == 1) {
> +             PRINTLN("Validation succeeded.");
> +             return;
> +     }
> +
> +     list_one(ins, nb_ins, pc, pc, NULL);
> +
> +     if (validation_success == 0)
> +             PRINTLN("Validation failed.");
> +}
> +
> +static void
> +debug_command_where(const struct ebpf_insn *ins, uint32_t nb_ins,
> +     int validation_success, uint32_t pc)
> +{
> +     for (uint32_t bi = 0; bi != branch_stack.length; ++bi) {
> +             const uint32_t jump_pc = branch_stack.branches[bi].jump_pc;
> +             const uint32_t target_pc = branch_stack.branches[bi].target_pc;
> +             const char *const comment =
> +                     !branch_stack.branches[bi].is_conditional ? NULL :
> +                     target_pc == jump_pc + 1 ? "fallen-through" : "taken";
> +             list_one(ins, nb_ins, jump_pc, UINT32_MAX, comment);
> +     }
> +     print_status(ins, nb_ins, validation_success, pc);
> +}
> +
> +/* Return true if pc is defined, otherwise print an error message. */
> +static bool
> +ensure_pc_defined(uint32_t pc)
> +{
> +     if (pc != UINT32_MAX)
> +             return true;
> +
> +     PRINTLN("No current instruction.");
> +     return false;
> +}
> +
> +/* Return true if still validating, otherwise print an error message. */
> +static bool
> +ensure_still_validating(int validation_success)
> +{
> +     if (validation_success == -EAGAIN)
> +             return true;
> +
> +     PRINTLN("Finished, use `start` or `run` to restart, `quit` to quit.");
> +     return false;
> +}
> +
> +/* Step-by-step validation callback: read and process user commands. */
> +static int
> +step_cb(struct rte_bpf_validate_debug *debug, __rte_unused void *ctx)
> +{
> +     int rc;
> +     int validation_success;
> +     const struct ebpf_insn *ins;
> +     uint32_t nb_ins, pc, list_offset;
> +
> +     validate_again = false;
> +
> +     rc = rte_bpf_validate_debug_get_ins(debug, &ins, &nb_ins);
> +     if (rc < 0) {
> +             PRINTLN("Error %d getting program instructions.", -rc);
> +             return rc;
> +     }
> +
> +     validation_success = get_validation_success(debug);
> +     if (validation_success < 0 && validation_success != -EAGAIN) {
> +             PRINTLN("Error %d getting validation result.",
> +                     -validation_success);
> +             return validation_success;
> +     }
> +
> +     pc = validation_success == 1 ? UINT32_MAX :
> +             rte_bpf_validate_debug_get_pc(debug);
> +
> +     print_status(ins, nb_ins, validation_success, pc);
> +
> +     list_offset = pc;
> +
> +     while (true) {
> +             switch (debug_command_get(PROMPT)) {
> +             case DEBUG_COMMAND_BREAK:
> +                     if (ensure_pc_defined(pc))
> +                             add_breakpoint(debug, nb_ins, pc);
> +                     continue;
> +             case DEBUG_COMMAND_BREAK_PC:
> +                     add_breakpoint(debug, nb_ins,
> debug_command_parsed.pc);
> +                     continue;
> +             case DEBUG_COMMAND_CATCH:
> +                     add_catchpoint(debug,
> +
>       parse_event_name(debug_command_parsed.event));
> +                     continue;
> +             case DEBUG_COMMAND_CLEAR:
> +                     if (ensure_pc_defined(pc))
> +                             point_infos_destroy_breakpoints(pc);
> +                     continue;
> +             case DEBUG_COMMAND_CLEAR_EVENT:
> +                     point_infos_destroy_catchpoints(
> +
>       parse_event_name(debug_command_parsed.event));
> +                     continue;
> +             case DEBUG_COMMAND_CLEAR_PC:
> +                     point_infos_destroy_breakpoints(
> +                             debug_command_parsed.pc);
> +                     continue;
> +             case DEBUG_COMMAND_CONTINUE:
> +                     if (ensure_still_validating(validation_success)) {
> +                             disable_step();
> +                             return 0;
> +                     }
> +                     continue;
> +             case DEBUG_COMMAND_DELETE:
> +                     point_infos_destroy_all();
> +                     continue;
> +             case DEBUG_COMMAND_DELETE_NUMBER:
> +                     point_infos_destroy_at(
> +                             debug_command_parsed.point_number);
> +                     continue;
> +             case DEBUG_COMMAND_INFO_FRAME:
> +                     print_frame(debug);
> +                     continue;
> +             case DEBUG_COMMAND_INFO_FRAME_OFFSET:
> +                     print_frame_offset(debug,
> +                             debug_command_parsed.frame_offset);
> +                     continue;
> +             case DEBUG_COMMAND_INFO_POINTS:
> +                     point_infos_print_all();
> +                     continue;
> +             case DEBUG_COMMAND_INFO_REGISTER:
> +                     print_register(debug,
> +
>       parse_register(debug_command_parsed.register_));
> +                     continue;
> +             case DEBUG_COMMAND_INFO_REGISTERS:
> +                     print_registers(debug);
> +                     continue;
> +             case DEBUG_COMMAND_LIST:
> +                     if (ensure_pc_defined(pc))
> +                             list(ins, nb_ins, &list_offset, 10, pc);
> +                     continue;
> +             case DEBUG_COMMAND_LIST_COUNT:
> +                     if (ensure_pc_defined(pc))
> +                             list(ins, nb_ins, &list_offset,
> +
>       debug_command_parsed.instruction_count, pc);
> +                     continue;
> +             case DEBUG_COMMAND_LIST_PROGRAM:
> +                     if (ensure_pc_defined(pc))
> +                             list(ins, nb_ins, NULL, UINT32_MAX, pc);
> +                     continue;
> +             case DEBUG_COMMAND_MAY_LITERAL_RHS:
> +                     print_if_may_literal_rhs(debug,
> +                             &debug_command_parsed.comparison);
> +                     continue;
> +             case DEBUG_COMMAND_MAY_REGISTER_RHS:
> +                     print_if_may_register_rhs(debug,
> +                             &debug_command_parsed.comparison);
> +                     continue;
> +             case DEBUG_COMMAND_EOF:
> +             case DEBUG_COMMAND_QUIT:
> +                     PRINTLN("Quitting...");
> +                     return validation_success == -EAGAIN ? -ECANCELED : 0;
> +             case DEBUG_COMMAND_RUN:
> +                     PRINTLN("Re-running...");
> +                     disable_step();
> +                     validate_again = true;
> +                     return -ECANCELED;
> +             case DEBUG_COMMAND_START:
> +                     PRINTLN("Re-starting...");
> +                     rc = enable_step(debug);
> +                     if (rc < 0)
> +                             return rc;
> +                     validate_again = true;
> +                     return -ECANCELED;
> +             case DEBUG_COMMAND_STEP:
> +                     if (ensure_still_validating(validation_success))
> +                             return 0;
> +                     continue;
> +             case DEBUG_COMMAND_WHERE:
> +                     debug_command_where(ins, nb_ins, validation_success,
> pc);
> +                     continue;
> +             default:
> +                     PRINTLN("INTERNAL ERROR");
> +                     return -ENOTSUP;
> +             }
> +     }
> +}
> +
> +/* Any point callback: print it and enable step-by-step validation. */
> +static int
> +point_cb(struct rte_bpf_validate_debug *debug, void *ctx)
> +{
> +     const uint32_t point_number = (uintptr_t)ctx;
> +     point_infos_print_at(point_number);
> +     return enable_step(debug);
> +}
> +
> +/* Branch stack machinery. */
> +
> +static void
> +clear_jump_always_step_point(void)
> +{
> +     rte_bpf_validate_debug_point_destroy(jump_always_step_point);
> +     jump_always_step_point = NULL;
> +}
> +
> +static int
> +reset_branch_tracking(struct rte_bpf_validate_debug *debug __rte_unused,
> +     void *ctx __rte_unused)
> +{
> +     branch_stack.length = 0;
> +     pending_jump_pc = UINT32_MAX;
> +     clear_jump_always_step_point();
> +     return 0;
> +}
> +
> +/*
> + * Handling the jump-always instructions:
> + * - upon a jump-always event save the pc and set a custom step callback;
> + * - ignore the first custom step callback call (still on the same jump);
> + * - on the second call push the jump pc into stack and delete the callback;
> + */
> +
> +static int
> +jump_always_step_cb(struct rte_bpf_validate_debug *debug, void *ctx
> __rte_unused)
> +{
> +     /* Step event is also emitted at the end of the jump instruction 
> itself. */
> +     if (rte_bpf_validate_debug_get_pc(debug) == pending_jump_pc)
> +             return 0;
> +
> +     branch_stack_append(&(struct branch_info){
> +             .jump_pc = pending_jump_pc,
> +             .target_pc = rte_bpf_validate_debug_get_pc(debug),
> +             .is_conditional = false,
> +     });
> +     clear_jump_always_step_point();
> +     return 0;
> +}
> +
> +static int
> +jump_always_cb(struct rte_bpf_validate_debug *debug, void *ctx __rte_unused)
> +{
> +     pending_jump_pc = rte_bpf_validate_debug_get_pc(debug);
> +     clear_jump_always_step_point();
> +     jump_always_step_point = rte_bpf_validate_debug_catch(debug,
> +             RTE_BPF_VALIDATE_DEBUG_EVENT_STEP,
> +             &(struct rte_bpf_validate_debug_callback){ jump_always_step_cb
> });
> +     return 0;
> +}
> +
> +/*
> + * Handling the jump-conditional instructions:
> + * - upon a jump-conditional event save the pc;
> + * - upon a branch-enter event push the conditional jump pc into stack;
> + * - upon a branch-return event pop conditional jump and all unconditional 
> jumps
> + *   preceding it from the stack;
> + * - during step execution notify the user about the step events;
> + */
> +
> +static int
> +jump_conditional_cb(struct rte_bpf_validate_debug *debug, void *ctx
> __rte_unused)
> +{
> +     pending_jump_pc = rte_bpf_validate_debug_get_pc(debug);
> +     return 0;
> +}
> +
> +static int
> +branch_enter_cb(struct rte_bpf_validate_debug *debug, void *ctx __rte_unused)
> +{
> +     branch_stack_append(&(struct branch_info){
> +             .jump_pc = pending_jump_pc,
> +             .target_pc = rte_bpf_validate_debug_get_pc(debug),
> +             .is_conditional = true,
> +     });
> +     if (is_step_enabled())
> +             PRINTLN("Entered new branch at pc %u.", pending_jump_pc);
> +     return 0;
> +}
> +
> +static int
> +branch_return_cb(struct rte_bpf_validate_debug *debug __rte_unused,
> +     void *ctx __rte_unused)
> +{
> +     clear_jump_always_step_point();
> +
> +     while (branch_stack.length > 0) {
> +             const struct branch_info branch_info =
> +                     branch_stack.branches[--branch_stack.length];
> +             pending_jump_pc = branch_info.jump_pc;
> +             if (branch_info.is_conditional)
> +                     break;
> +     }
> +     if (is_step_enabled())
> +             PRINTLN("Returned from branch at pc %u.", pending_jump_pc);
> +     return 0;
> +}
> +
> +/* Notify user when skipping branches in step mode. */
> +
> +static int
> +branch_prune_cb(struct rte_bpf_validate_debug *debug __rte_unused,
> +     void *ctx __rte_unused)
> +{
> +     if (is_step_enabled())
> +             PRINTLN("Prunned branch at pc %u.", pending_jump_pc);
> +     return 0;
> +}
> +
> +static int
> +branch_unreachable_cb(struct rte_bpf_validate_debug *debug __rte_unused,
> +     void *ctx __rte_unused)
> +{
> +     if (is_step_enabled())
> +             PRINTLN("Unreachable branch at pc %u.", pending_jump_pc);
> +     return 0;
> +}
> +
> +/* Global initialization and cleanup functions. */
> +
> +static int
> +set_callbacks(struct rte_bpf_validate_debug *debug)
> +{
> +     static const struct rte_bpf_validate_debug_callback events_callback[
> +                     RTE_BPF_VALIDATE_DEBUG_EVENT_END] = {
> +             [RTE_BPF_VALIDATE_DEBUG_EVENT_VALIDATION_START] = {
> reset_branch_tracking },
> +             [RTE_BPF_VALIDATE_DEBUG_EVENT_VALIDATION_SUCCESS] = {
> step_cb },
> +             [RTE_BPF_VALIDATE_DEBUG_EVENT_VALIDATION_FAILURE] = {
> step_cb },
> +             [RTE_BPF_VALIDATE_DEBUG_EVENT_JUMP_CONDITIONAL] = {
> jump_conditional_cb },
> +             [RTE_BPF_VALIDATE_DEBUG_EVENT_JUMP_ALWAYS] = {
> jump_always_cb },
> +             [RTE_BPF_VALIDATE_DEBUG_EVENT_BRANCH_ENTER] = {
> branch_enter_cb },
> +             [RTE_BPF_VALIDATE_DEBUG_EVENT_BRANCH_RETURN] = {
> branch_return_cb },
> +             [RTE_BPF_VALIDATE_DEBUG_EVENT_BRANCH_PRUNE] = {
> branch_prune_cb },
> +             [RTE_BPF_VALIDATE_DEBUG_EVENT_BRANCH_UNREACHABLE] = {
> branch_unreachable_cb },
> +     };
> +
> +     for (enum rte_bpf_validate_debug_event event = 0;
> +                     event < RTE_BPF_VALIDATE_DEBUG_EVENT_END;
> ++event)
> +             if (events_callback[event].fn != NULL &&
> +                             rte_bpf_validate_debug_catch(debug, event,
> +                                     &events_callback[event]) == NULL)
> +                     return -rte_errno;
> +
> +     return 0;
> +}
> +
> +struct rte_bpf_validate_debug *
> +debug_create(void)
> +{
> +
> +     int rc = 0;
> +
> +     struct rte_bpf_validate_debug *const debug =
> rte_bpf_validate_debug_create();
> +     if (debug == NULL)
> +             rc = -rte_errno;
> +
> +     rc = rc < 0 ? rc : set_callbacks(debug);
> +
> +     rc = rc < 0 ? rc : enable_step(debug);
> +
> +     if (rc < 0) {
> +             debug_destroy(debug);
> +             rte_errno = -rc;
> +             return NULL;
> +     }
> +
> +     return debug;
> +}
> +
> +void
> +debug_destroy(struct rte_bpf_validate_debug *debug)
> +{
> +     /* No need to destroy created points, destroying debug will do it. */
> +     point_infos_free();
> +     branch_stack_free();
> +     rte_bpf_validate_debug_destroy(debug);
> +}
> +
> +bool
> +debug_validate_again(void)
> +{
> +     return validate_again;
> +}
> diff --git a/app/validate-bpf/debug_command.c b/app/validate-
> bpf/debug_command.c
> new file mode 100644
> index 000000000000..505ae389c94f
> --- /dev/null
> +++ b/app/validate-bpf/debug_command.c
> @@ -0,0 +1,383 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2025 Huawei Technologies Co., Ltd
> + */
> +
> +#include "debug_command.h"
> +
> +#include <rte_debug.h>
> +#include <cmdline.h>
> +#include <cmdline_socket.h>
> +#include <cmdline_parse_num.h>
> +
> +#include <string.h>
> +
> +
> +#define EVENT_PATTERN \
> +     "invalid-state#" \
> +     "branch-enter#branch-prune#branch-return#branch-unreachable#" \
> +     "jump-always#jump-conditional"
> +
> +#define REGISTER_PATTERN "r0#r1#r2#r3#r4#r5#r6#r7#r8#r9#r10"
> +
> +#define COMPARISON_OP_PATTERN "==#!=#<#<=#>#>=#s<#s<=#s>#s>="
> +
> +static void
> +handle_command(void *parsed_result, struct cmdline *cl, void *data);
> +
> +/* Keywords */
> +static cmdline_parse_token_string_t break_tok =
> +     TOKEN_STRING_INITIALIZER(struct debug_command_parsed, keyword,
> "b#break");
> +static cmdline_parse_token_string_t may_tok =
> +     TOKEN_STRING_INITIALIZER(struct debug_command_parsed, keyword,
> "may");
> +static cmdline_parse_token_string_t catch_tok =
> +     TOKEN_STRING_INITIALIZER(struct debug_command_parsed, keyword,
> "catch");
> +static cmdline_parse_token_string_t clear_tok =
> +     TOKEN_STRING_INITIALIZER(struct debug_command_parsed, keyword,
> "clear");
> +static cmdline_parse_token_string_t continue_tok =
> +     TOKEN_STRING_INITIALIZER(struct debug_command_parsed, keyword,
> "c#continue");
> +static cmdline_parse_token_string_t delete_tok =
> +     TOKEN_STRING_INITIALIZER(struct debug_command_parsed, keyword,
> "delete");
> +static cmdline_parse_token_string_t info_tok =
> +     TOKEN_STRING_INITIALIZER(struct debug_command_parsed, keyword,
> "i#info");
> +static cmdline_parse_token_string_t info_points_tok =
> +     TOKEN_STRING_INITIALIZER(struct debug_command_parsed, keyword,
> +             "b#break#breakpoints#points");
> +static cmdline_parse_token_string_t info_frame_tok =
> +     TOKEN_STRING_INITIALIZER(struct debug_command_parsed, keyword,
> "f#frame");
> +static cmdline_parse_token_string_t info_registers_tok =
> +     TOKEN_STRING_INITIALIZER(struct debug_command_parsed, keyword,
> "r#registers");
> +static cmdline_parse_token_string_t list_tok =
> +     TOKEN_STRING_INITIALIZER(struct debug_command_parsed, keyword,
> "l#list");
> +static cmdline_parse_token_string_t program_tok =
> +     TOKEN_STRING_INITIALIZER(struct debug_command_parsed, keyword,
> "program");
> +static cmdline_parse_token_string_t quit_tok =
> +     TOKEN_STRING_INITIALIZER(struct debug_command_parsed, keyword,
> "q#quit");
> +static cmdline_parse_token_string_t run_tok =
> +     TOKEN_STRING_INITIALIZER(struct debug_command_parsed, keyword,
> "run");
> +static cmdline_parse_token_string_t start_tok =
> +     TOKEN_STRING_INITIALIZER(struct debug_command_parsed, keyword,
> "start");
> +static cmdline_parse_token_string_t step_tok =
> +     TOKEN_STRING_INITIALIZER(struct debug_command_parsed, keyword,
> "s#step");
> +static cmdline_parse_token_string_t where_tok =
> +     TOKEN_STRING_INITIALIZER(struct debug_command_parsed, keyword,
> "where");
> +
> +/* Variable tokens */
> +static cmdline_parse_token_string_t comparison_lhs_tok =
> +     TOKEN_STRING_INITIALIZER(struct debug_command_parsed,
> comparison.lhs, REGISTER_PATTERN);
> +static cmdline_parse_token_string_t comparison_op_tok =
> +     TOKEN_STRING_INITIALIZER(struct debug_command_parsed,
> comparison.op, COMPARISON_OP_PATTERN);
> +static cmdline_parse_token_num_t comparison_rhs_literal_tok =
> +     TOKEN_NUM_INITIALIZER(struct debug_command_parsed,
> comparison.literal_rhs, RTE_INT64);
> +static cmdline_parse_token_string_t comparison_rhs_register_tok =
> +     TOKEN_STRING_INITIALIZER(struct debug_command_parsed,
> comparison.register_rhs,
> +             REGISTER_PATTERN);
> +static cmdline_parse_token_string_t event_tok =
> +     TOKEN_STRING_INITIALIZER(struct debug_command_parsed, event,
> EVENT_PATTERN);
> +static cmdline_parse_token_num_t frame_offset_tok =
> +     TOKEN_NUM_INITIALIZER(struct debug_command_parsed, frame_offset,
> RTE_INT64);
> +static cmdline_parse_token_num_t instruction_count_tok =
> +     TOKEN_NUM_INITIALIZER(struct debug_command_parsed,
> instruction_count, RTE_UINT32);
> +static cmdline_parse_token_num_t pc_tok =
> +     TOKEN_NUM_INITIALIZER(struct debug_command_parsed,
> instruction_offset, RTE_UINT32);
> +static cmdline_parse_token_num_t point_number_tok =
> +     TOKEN_NUM_INITIALIZER(struct debug_command_parsed, point_number,
> RTE_UINT32);
> +static cmdline_parse_token_string_t register_tok =
> +     TOKEN_STRING_INITIALIZER(struct debug_command_parsed, register_,
> REGISTER_PATTERN);
> +
> +
> +/* Commands */
> +static cmdline_parse_inst_t cmd_break = {
> +     .f = handle_command,
> +     .data = (void *)DEBUG_COMMAND_BREAK,
> +     .help_str = "b|break: break at current instruction",
> +     .tokens = {
> +             (void *)&break_tok,
> +             NULL,
> +     }
> +};
> +static cmdline_parse_inst_t cmd_break_pc = {
> +     .f = handle_command,
> +     .data = (void *)DEBUG_COMMAND_BREAK_PC,
> +     .help_str = "b|break <pc>: break at specified instruction",
> +     .tokens = {
> +             (void *)&break_tok,
> +             (void *)&pc_tok,
> +             NULL,
> +     }
> +};
> +static cmdline_parse_inst_t cmd_catch = {
> +     .f = handle_command,
> +     .data = (void *)DEBUG_COMMAND_CATCH,
> +     .help_str = "catch <event>: catch specified event",
> +     .tokens = {
> +             (void *)&catch_tok,
> +             (void *)&event_tok,
> +             NULL,
> +     }
> +};
> +static cmdline_parse_inst_t cmd_clear = {
> +     .f = handle_command,
> +     .data = (void *)DEBUG_COMMAND_CLEAR,
> +     .help_str = "clear: delete all breakpoints at current instruction",
> +     .tokens = {
> +             (void *)&clear_tok,
> +             NULL,
> +     }
> +};
> +static cmdline_parse_inst_t cmd_clear_event = {
> +     .f = handle_command,
> +     .data = (void *)DEBUG_COMMAND_CLEAR_EVENT,
> +     .help_str = "clear <event>: delete all catchpoints for specified event",
> +     .tokens = {
> +             (void *)&clear_tok,
> +             (void *)&event_tok,
> +             NULL,
> +     }
> +};
> +static cmdline_parse_inst_t cmd_clear_pc = {
> +     .f = handle_command,
> +     .data = (void *)DEBUG_COMMAND_CLEAR_PC,
> +     .help_str = "clear <pc>: delete all breakpoints at specified 
> instruction",
> +     .tokens = {
> +             (void *)&clear_tok,
> +             (void *)&pc_tok,
> +             NULL,
> +     }
> +};
> +static cmdline_parse_inst_t cmd_continue = {
> +     .f = handle_command,
> +     .data = (void *)DEBUG_COMMAND_CONTINUE,
> +     .help_str = "c|continue: continue validation",
> +     .tokens = {
> +             (void *)&continue_tok,
> +             NULL,
> +     }
> +};
> +static cmdline_parse_inst_t cmd_delete = {
> +     .f = handle_command,
> +     .data = (void *)DEBUG_COMMAND_DELETE,
> +     .help_str = "delete: delete all breakpoints and catchpoints",
> +     .tokens = {
> +             (void *)&delete_tok,
> +             NULL,
> +     }
> +};
> +static cmdline_parse_inst_t cmd_delete_number = {
> +     .f = handle_command,
> +     .data = (void *)DEBUG_COMMAND_DELETE_NUMBER,
> +     .help_str = "delete <point>: delete specified breakpoint or catchpoint",
> +     .tokens = {
> +             (void *)&delete_tok,
> +             (void *)&point_number_tok,
> +             NULL,
> +     }
> +};
> +static cmdline_parse_inst_t cmd_info_frame = {
> +     .f = handle_command,
> +     .data = (void *)DEBUG_COMMAND_INFO_FRAME,
> +     .help_str = "i|info f|frame: show information about all frame 
> locations",
> +     .tokens = {
> +             (void *)&info_tok,
> +             (void *)&info_frame_tok,
> +             NULL,
> +     }
> +};
> +static cmdline_parse_inst_t cmd_info_frame_offset = {
> +     .f = handle_command,
> +     .data = (void *)DEBUG_COMMAND_INFO_FRAME_OFFSET,
> +     .help_str = "i|info f|frame -<offset>: show information about specified
> frame location",
> +     .tokens = {
> +             (void *)&info_tok,
> +             (void *)&info_frame_tok,
> +             (void *)&frame_offset_tok,
> +             NULL,
> +     }
> +};
> +static cmdline_parse_inst_t cmd_info_points = {
> +     .f = handle_command,
> +     .data = (void *)DEBUG_COMMAND_INFO_POINTS,
> +     .help_str = "i|info b|break|breakpoints|points: "
> +             "show information about all breakpoints and catchpoints",
> +     .tokens = {
> +             (void *)&info_tok,
> +             (void *)&info_points_tok,
> +             NULL,
> +     }
> +};
> +static cmdline_parse_inst_t cmd_info_register = {
> +     .f = handle_command,
> +     .data = (void *)DEBUG_COMMAND_INFO_REGISTER,
> +     .help_str = "i|info <register>: show information about specified 
> register",
> +     .tokens = {
> +             (void *)&info_tok,
> +             (void *)&register_tok,
> +             NULL,
> +     }
> +};
> +static cmdline_parse_inst_t cmd_info_registers = {
> +     .f = handle_command,
> +     .data = (void *)DEBUG_COMMAND_INFO_REGISTERS,
> +     .help_str = "i|info r|registers: show information about all registers",
> +     .tokens = {
> +             (void *)&info_tok,
> +             (void *)&info_registers_tok,
> +             NULL,
> +     }
> +};
> +static cmdline_parse_inst_t cmd_list = {
> +     .f = handle_command,
> +     .data = (void *)DEBUG_COMMAND_LIST,
> +     .help_str = "l|list: list ten instructions",
> +     .tokens = {
> +             (void *)&list_tok,
> +             NULL,
> +     }
> +};
> +static cmdline_parse_inst_t cmd_list_count = {
> +     .f = handle_command,
> +     .data = (void *)DEBUG_COMMAND_LIST_COUNT,
> +     .help_str = "l|list <number>: list specified number of instructions",
> +     .tokens = {
> +             (void *)&list_tok,
> +             (void *)&instruction_count_tok,
> +             NULL,
> +     }
> +};
> +static cmdline_parse_inst_t cmd_list_program = {
> +     .f = handle_command,
> +     .data = (void *)DEBUG_COMMAND_LIST_PROGRAM,
> +     .help_str = "l|list program: list whole program",
> +     .tokens = {
> +             (void *)&list_tok,
> +             (void *)&program_tok,
> +             NULL,
> +     }
> +};
> +static cmdline_parse_inst_t cmd_may_literal_rhs = {
> +     .f = handle_command,
> +     .data = (void *)DEBUG_COMMAND_MAY_LITERAL_RHS,
> +     .help_str = "may <register> <comparison> <number>: "
> +             "check if specified condition _may_ be true",
> +     .tokens = {
> +             (void *)&may_tok,
> +             (void *)&comparison_lhs_tok,
> +             (void *)&comparison_op_tok,
> +             (void *)&comparison_rhs_literal_tok,
> +             NULL,
> +     }
> +};
> +static cmdline_parse_inst_t cmd_may_register_rhs = {
> +     .f = handle_command,
> +     .data = (void *)DEBUG_COMMAND_MAY_REGISTER_RHS,
> +     .help_str = "may <register> <comparison> <register>: "
> +             "check if specified condition _may_ be true",
> +     .tokens = {
> +             (void *)&may_tok,
> +             (void *)&comparison_lhs_tok,
> +             (void *)&comparison_op_tok,
> +             (void *)&comparison_rhs_register_tok,
> +             NULL,
> +     }
> +};
> +static cmdline_parse_inst_t cmd_quit = {
> +     .f = handle_command,
> +     .data = (void *)DEBUG_COMMAND_QUIT,
> +     .help_str = "quit: q|quit debugger",
> +     .tokens = {
> +             (void *)&quit_tok,
> +             NULL,
> +     }
> +};
> +static cmdline_parse_inst_t cmd_run = {
> +     .f = handle_command,
> +     .data = (void *)DEBUG_COMMAND_RUN,
> +     .help_str = "run: re-run validation from the start",
> +     .tokens = {
> +             (void *)&run_tok,
> +             NULL,
> +     }
> +};
> +static cmdline_parse_inst_t cmd_start = {
> +     .f = handle_command,
> +     .data = (void *)DEBUG_COMMAND_START,
> +     .help_str = "start: re-start validation and stop at start",
> +     .tokens = {
> +             (void *)&start_tok,
> +             NULL,
> +     }
> +};
> +static cmdline_parse_inst_t cmd_step = {
> +     .f = handle_command,
> +     .data = (void *)DEBUG_COMMAND_STEP,
> +     .help_str = "s|step: validate one instruction",
> +     .tokens = {
> +             (void *)&step_tok,
> +             NULL,
> +     }
> +};
> +static cmdline_parse_inst_t cmd_where = {
> +     .f = handle_command,
> +     .data = (void *)DEBUG_COMMAND_WHERE,
> +     .help_str = "where: show current branch stack",
> +     .tokens = {
> +             (void *)&where_tok,
> +             NULL,
> +     }
> +};
> +
> +static cmdline_parse_ctx_t debug_ctx[] = {
> +     &cmd_break,
> +     &cmd_break_pc,
> +     &cmd_catch,
> +     &cmd_clear,
> +     &cmd_clear_event,
> +     &cmd_clear_pc,
> +     &cmd_continue,
> +     &cmd_delete,
> +     &cmd_delete_number,
> +     &cmd_info_frame,
> +     &cmd_info_frame_offset,
> +     &cmd_info_points,
> +     &cmd_info_register,
> +     &cmd_info_registers,
> +     &cmd_list,
> +     &cmd_list_count,
> +     &cmd_list_program,
> +     &cmd_may_literal_rhs,
> +     &cmd_may_register_rhs,
> +     &cmd_quit,
> +     &cmd_run,
> +     &cmd_start,
> +     &cmd_step,
> +     &cmd_where,
> +     NULL
> +};
> +
> +/* Receive, fill and return one command. */
> +
> +static enum debug_command debug_command;
> +
> +struct debug_command_parsed debug_command_parsed;
> +
> +static void
> +handle_command(void *parsed_result, struct cmdline *cl, void *data)
> +{
> +     RTE_BUILD_BUG_ON(sizeof(debug_command_parsed) >
> CMDLINE_PARSE_RESULT_BUFSIZE);
> +     memcpy(&debug_command_parsed, parsed_result,
> sizeof(debug_command_parsed));
> +     debug_command = (uintptr_t)data;
> +     cmdline_quit(cl);
> +}
> +
> +enum debug_command
> +debug_command_get(const char *prompt)
> +{
> +     debug_command = DEBUG_COMMAND_EOF;
> +     struct cmdline *const cmdline = cmdline_stdin_new(debug_ctx, prompt);
> +     RTE_VERIFY(cmdline != NULL);
> +     cmdline_interact(cmdline);
> +     cmdline_stdin_exit(cmdline);
> +     /* Clear prompt, or it would prepend first message that follows. */
> +     printf("\r%*s\r", (int)strlen(prompt), "");
> +     fflush(stdout);
> +     return debug_command;
> +}
> diff --git a/app/validate-bpf/debug_command.h b/app/validate-
> bpf/debug_command.h
> new file mode 100644
> index 000000000000..1a59ab251ebb
> --- /dev/null
> +++ b/app/validate-bpf/debug_command.h
> @@ -0,0 +1,65 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2025 Huawei Technologies Co., Ltd
> + */
> +
> +#include <cmdline_parse_string.h>
> +
> +#include <stdint.h>
> +
> +
> +struct debug_command_comparison {
> +     cmdline_fixed_string_t lhs;
> +     cmdline_fixed_string_t op;
> +     union {
> +             cmdline_fixed_string_t register_rhs;
> +             int64_t literal_rhs;
> +     };
> +};
> +
> +struct debug_command_parsed {
> +     cmdline_fixed_string_t keyword;  /* Any keyword we don't need */
> +     union {
> +             struct debug_command_comparison comparison;
> +             cmdline_fixed_string_t event;
> +             int32_t frame_offset;
> +             uint32_t instruction_count;
> +             uint32_t instruction_offset;
> +             uint32_t pc;
> +             uint32_t point_number;
> +             cmdline_fixed_string_t register_;
> +     };
> +};
> +
> +enum debug_command {
> +     DEBUG_COMMAND_EOF,
> +     DEBUG_COMMAND_BREAK,
> +     DEBUG_COMMAND_BREAK_PC,
> +     DEBUG_COMMAND_CATCH,
> +     DEBUG_COMMAND_CLEAR,
> +     DEBUG_COMMAND_CLEAR_EVENT,
> +     DEBUG_COMMAND_CLEAR_PC,
> +     DEBUG_COMMAND_CONTINUE,
> +     DEBUG_COMMAND_DELETE,
> +     DEBUG_COMMAND_DELETE_NUMBER,
> +     DEBUG_COMMAND_INFO_FRAME,
> +     DEBUG_COMMAND_INFO_FRAME_OFFSET,
> +     DEBUG_COMMAND_INFO_POINTS,
> +     DEBUG_COMMAND_INFO_REGISTER,
> +     DEBUG_COMMAND_INFO_REGISTERS,
> +     DEBUG_COMMAND_LIST,
> +     DEBUG_COMMAND_LIST_COUNT,
> +     DEBUG_COMMAND_LIST_PROGRAM,
> +     DEBUG_COMMAND_MAY_LITERAL_RHS,
> +     DEBUG_COMMAND_MAY_REGISTER_RHS,
> +     DEBUG_COMMAND_QUIT,
> +     DEBUG_COMMAND_RUN,
> +     DEBUG_COMMAND_START,
> +     DEBUG_COMMAND_STEP,
> +     DEBUG_COMMAND_WHERE,
> +};
> +
> +extern struct debug_command_parsed debug_command_parsed;
> +
> +/** Get and return one command line command, storing its data in the struct
> above. */
> +enum debug_command
> +debug_command_get(const char *prompt);
> diff --git a/app/validate-bpf/eal_init_args.c 
> b/app/validate-bpf/eal_init_args.c
> new file mode 100644
> index 000000000000..6ce9d561e0ce
> --- /dev/null
> +++ b/app/validate-bpf/eal_init_args.c
> @@ -0,0 +1,57 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2025 Huawei Technologies Co., Ltd
> + */
> +
> +#include "internal.h"
> +
> +
> +#define APP_EAL_INIT_ARG_SIZE_MAX sizeof("--log-level=lib.eal:warning")
> +
> +static const char APP_EAL_INIT_ARGS[][APP_EAL_INIT_ARG_SIZE_MAX] = {
> +     "--log-level=lib.eal:warning",
> +     "--no-huge",
> +     "--no-pci",
> +     "--no-hpet",
> +     "--no-shconf",
> +};
> +
> +#define APP_EAL_INIT_ARGC (/* program name */ 1 +
> RTE_DIM(APP_EAL_INIT_ARGS))
> +
> +int
> +get_eal_init_argc(void)
> +{
> +     return APP_EAL_INIT_ARGC;
> +}
> +
> +/*
> + * We cannot just return literal strings here, because rte_eal_init accepts
> + * an array of mutable pointers to mutable strings, so literals won't work.
> + * Instead we build a copy of APP_EAL_INIT_ARGS in a static mutable area.
> + */
> +char**
> +get_eal_init_argv(char *prog_name)
> +{
> +     /* Static arrays for mutable copies of actual args */
> +     static char mutable_args[RTE_DIM(APP_EAL_INIT_ARGS)]
> +             [APP_EAL_INIT_ARG_SIZE_MAX];
> +     /*
> +      * Static array for pointers to args. First element will hold pointer
> +      * to the prog_name, last will be set to NULL, the rest will point to
> +      * elements of mutable_args with index one smaller.
> +      */
> +     static char *mutable_ptrs[APP_EAL_INIT_ARGC + /* terminating NULL */ 1];
> +
> +     mutable_ptrs[0] = prog_name;
> +     for (int argi = 0; argi != RTE_DIM(APP_EAL_INIT_ARGS); ++argi) {
> +             const char * const const_arg = APP_EAL_INIT_ARGS[argi];
> +             char * const mutable_arg = mutable_args[argi];
> +             const int snprintf_rc = snprintf(mutable_arg,
> +                     APP_EAL_INIT_ARG_SIZE_MAX, "%s", const_arg);
> +             RTE_VERIFY(snprintf_rc >= 0 &&
> +                     (size_t)snprintf_rc < APP_EAL_INIT_ARG_SIZE_MAX);
> +             mutable_ptrs[1 + argi] = mutable_arg;
> +     }
> +     mutable_ptrs[RTE_DIM(mutable_ptrs) - 1] = NULL;
> +
> +     return mutable_ptrs;
> +}
> diff --git a/app/validate-bpf/internal.h b/app/validate-bpf/internal.h
> new file mode 100644
> index 000000000000..e134e683142a
> --- /dev/null
> +++ b/app/validate-bpf/internal.h
> @@ -0,0 +1,151 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2025 Huawei Technologies Co., Ltd
> + */
> +
> +#include <rte_bpf.h>
> +#include <rte_log.h>
> +
> +
> +extern int validate_bpf_logtype;
> +#define RTE_LOGTYPE_VALIDATE_BPF validate_bpf_logtype
> +#define VALIDATE_BPF_LOG(level, ...) \
> +     RTE_LOG_LINE(level, VALIDATE_BPF, "" __VA_ARGS__)
> +
> +struct rte_bpf_validate_debug;
> +
> +/**
> + * List of pointers to allocations, to keep track of things to free.
> + *
> + * May contain NULLs. May contain itself directly or via an outer struct.
> + */
> +struct alloc_list {
> +     size_t count;
> +     void **ptrs;
> +};
> +
> +/** Add new pointer to the alloc list, return its index. */
> +size_t alloc_list_append(struct alloc_list *alloc_list, void *ptr);
> +
> +/** Replace pointer at the specified index in the alloc list. */
> +void alloc_list_replace(struct alloc_list *alloc_list, size_t index, void 
> *ptr);
> +
> +/** Free all allocations in the alloc list */
> +void alloc_list_free_all(struct alloc_list *alloc_list);
> +
> +
> +/** Parsed program arguments */
> +struct args {
> +     /* Allocations list, args.c internal use only. */
> +     struct alloc_list _alloc_list;
> +
> +     /* Set if command line contains --help */
> +     bool show_help;
> +
> +     /* BPF load parameters. */
> +     struct rte_bpf_prm_ex bpf_prm;
> +
> +     /* Size of the mbuf data buffer. */
> +     size_t mbuf_buf_size;
> +};
> +
> +/** Print program usage information */
> +void
> +print_usage(const char *program_name);
> +
> +/** Print program defaults. */
> +void
> +print_defaults(void);
> +
> +/**
> + * Parse command-line arguments
> + *
> + * @param argc
> + *   Command-line arguments count, as received by main.
> + * @param argv
> + *   Command-line arguments array, as received by main.
> + *   Modified during the call due to the use of getopt_long.
> + *   Modifying it after the call invalidates the return value.
> + * @return
> + *   - parse results in case of success;
> + *   - NULL in case of an error (diagnostic will be printed to stderr)
> + */
> +struct args *
> +args_parse(int argc, char *argv[]);
> +
> +/** Destroy args struct returned by args_parse */
> +void
> +args_destroy(struct args *args);
> +
> +
> +/** Value for the rte_eal_init argc argument */
> +int
> +get_eal_init_argc(void);
> +
> +/**
> + * Value for the rte_eal_init argv argument
> + *
> + * @param prog_name
> + *   Program name, can be obtained from argv[0]
> + */
> +char **
> +get_eal_init_argv(char *prog_name);
> +
> +
> +/** Print types supported in command-line. */
> +void
> +print_supported_types(void);
> +
> +/**
> + * Parse text into arg.
> + * @param arg
> + *   Pointer to a variable to put parse result to.
> + *   Member `buf_size` of types related to `struct rte_mbuf` is set to
> + *   `RTE_MBUF_DEFAULT_BUF_SIZE`, adjust using `adjust_arg_buf_size` if
> needed.
> + * @param text
> + *   Text representation of the type, e.g. "struct rte_mbuf *".
> + * @param alloc_list
> + *   Alloc list to use for new allocations.
> + * @return
> + *   0 on success
> + *   -1 on failure
> + */
> +int
> +parse_arg(struct rte_bpf_arg *arg, const char *text);
> +
> +/**
> + * Parse text into xsym.
> + * @param arg
> + *   Pointer to a variable to put parse result to.
> + *   Member `buf_size` of types related to `struct rte_mbuf` is set to
> + *   `RTE_MBUF_DEFAULT_BUF_SIZE`, adjust using `adjust_xsym_buf_size` if
> needed.
> + * @param text
> + *   Text representation of the external symbol, e.g. "void exit(uint32_t)".
> + * @param alloc_list
> + *   Alloc list to use for new allocations.
> + * @return
> + *   0 on success
> + *   -1 on failure
> + */
> +int
> +parse_xsym(struct rte_bpf_xsym *xsym, const char *text,
> +     struct alloc_list *alloc_list);
> +
> +/** If arg->buf_size is non-zero, set it to mbuf_buf_size */
> +void
> +adjust_arg_buf_size(struct rte_bpf_arg *arg, size_t mbuf_buf_size);
> +
> +/** If buf_size members are non-zero, set them to mbuf_buf_size */
> +void
> +adjust_xsym_buf_size(struct rte_bpf_xsym *xsym, size_t mbuf_buf_size);
> +
> +/** Create and set up global debugging session. */
> +struct rte_bpf_validate_debug *
> +debug_create(void);
> +
> +/** Clear and destroy global debugging session. */
> +void
> +debug_destroy(struct rte_bpf_validate_debug *debug);
> +
> +/** Tells caller if validation should be re-tried. */
> +bool
> +debug_validate_again(void);
> diff --git a/app/validate-bpf/main.c b/app/validate-bpf/main.c
> new file mode 100644
> index 000000000000..c2f73993767b
> --- /dev/null
> +++ b/app/validate-bpf/main.c
> @@ -0,0 +1,90 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2025 Huawei Technologies Co., Ltd
> + */
> +
> +#include "internal.h"
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +
> +#include <rte_bpf.h>
> +#include <rte_debug.h>
> +#include <rte_eal.h>
> +#include <rte_errno.h>
> +
> +RTE_LOG_REGISTER(validate_bpf_logtype, validate-bpf, NOTICE);
> +
> +static int
> +test_bpf_load(struct rte_bpf_prm_ex *prm)
> +{
> +     struct rte_bpf * const bpf = rte_bpf_load_ex(prm);
> +
> +     const int rc = -rte_errno;
> +
> +     rte_bpf_destroy(bpf);
> +
> +     return bpf == NULL ? rc : 0;
> +}
> +
> +/* Re-starts validation of asked from interactive debugger. */
> +static int
> +test_bpf_load_with_restarts(struct rte_bpf_prm_ex *prm)
> +{
> +     for (;;) {
> +             const int rc = test_bpf_load(prm);
> +
> +             if (rc == -ECANCELED && debug_validate_again())
> +                     continue;
> +
> +             if (rc != 0)
> +                     fprintf(stderr, "Error %d loading BPF: %s\n",
> +                             -rc, strerror(-rc));
> +             else
> +                     fprintf(stderr, "Validation succeeded.\n");
> +
> +             return rc;
> +     }
> +}
> +
> +int
> +main(int argc, char *argv[])
> +{
> +     int rc;
> +
> +     struct args * const args = args_parse(argc, argv);
> +     if (args == NULL || args->show_help
> +                     || args->bpf_prm.elf_file.path == NULL) {
> +             args_destroy(args);
> +             print_usage(argv[0]);
> +             if (args == NULL)
> +                     /* Could not parse arguments. */
> +                     return 2;
> +             print_supported_types();
> +             print_defaults();
> +             return 0;
> +     }
> +
> +     const int eal_init_argc = get_eal_init_argc();
> +     char ** const eal_init_argv = get_eal_init_argv(argv[0]);
> +     rc = rte_eal_init(eal_init_argc, eal_init_argv);
> +     if (rc < 0) {
> +             fprintf(stderr, "Error %d initializing EAL: %s\n",
> +                     rte_errno, strerror(rte_errno));
> +             args_destroy(args);
> +             return EXIT_FAILURE;
> +     }
> +     RTE_VERIFY(rc == eal_init_argc - 1);
> +
> +     const int ret = test_bpf_load_with_restarts(&args->bpf_prm);
> +
> +     args_destroy(args);
> +
> +     rc = rte_eal_cleanup();
> +     if (rc < 0) {
> +             fprintf(stderr, "Error %d cleaning up EAL: %s\n",
> +                     rte_errno, strerror(rte_errno));
> +             return EXIT_FAILURE;
> +     }
> +
> +     return ret < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
> +}
> diff --git a/app/validate-bpf/meson.build b/app/validate-bpf/meson.build
> new file mode 100644
> index 000000000000..03cfc329efb5
> --- /dev/null
> +++ b/app/validate-bpf/meson.build
> @@ -0,0 +1,13 @@
> +# SPDX-License-Identifier: BSD-3-Clause
> +# Copyright(c) 2025 Huawei Technologies Co., Ltd
> +
> +sources = files(
> +        'alloc_list.c',
> +        'args.c',
> +        'debug.c',
> +        'debug_command.c',
> +        'eal_init_args.c',
> +        'main.c',
> +        'parse_decl.c',
> +)
> +deps = ['bpf', 'cmdline']
> diff --git a/app/validate-bpf/parse_decl.c b/app/validate-bpf/parse_decl.c
> new file mode 100644
> index 000000000000..be733d58cd41
> --- /dev/null
> +++ b/app/validate-bpf/parse_decl.c
> @@ -0,0 +1,623 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2025 Huawei Technologies Co., Ltd
> + */
> +
> +#include "internal.h"
> +
> +#include <ctype.h>
> +#include <stdlib.h>
> +
> +#include <rte_ether.h>
> +#include <rte_ip.h>
> +#include <rte_mbuf_core.h>
> +#include <rte_tcp.h>
> +#include <rte_udp.h>
> +
> +#define RETURN_TEXT_ERROR(text, text_start, message, ...) do {               
>  \
> +     const int _offset = (text) - (text_start);                            \
> +     VALIDATE_BPF_LOG(ERR, "at offset %d: " message,                       \
> +             _offset, ## __VA_ARGS__);                                     \
> +     VALIDATE_BPF_LOG(NOTICE, "%s", text_start);                           \
> +     VALIDATE_BPF_LOG(NOTICE, "%*c", _offset + 1, '^');                    \
> +     return -1;                                                            \
> +} while (0)
> +
> +/* Used in place of any signature, this is not really being checked. */
> +static uint64_t
> +dummy_function(uint64_t arg1, uint64_t arg2, uint64_t arg3, uint64_t arg4,
> +     uint64_t arg5)
> +{
> +     RTE_SET_USED(arg1);
> +     RTE_SET_USED(arg2);
> +     RTE_SET_USED(arg3);
> +     RTE_SET_USED(arg4);
> +     RTE_SET_USED(arg5);
> +     return 0;
> +}
> +
> +
> +/* TOKENS AND TYPES */
> +
> +enum token {
> +     TOKEN_UNRECOGNIZED = -1,
> +     TOKEN_END = 0,
> +
> +     TOKEN_ASTERISK,
> +     TOKEN_BRACKET_CLOSE,
> +     TOKEN_BRACKET_OPEN,
> +     TOKEN_COMMA,
> +     TOKEN_PARENTHESIS_CLOSE,
> +     TOKEN_PARENTHESIS_OPEN,
> +     TOKEN_STRUCT,
> +
> +     TOKEN_TYPES_BEGIN,
> +
> +     TOKEN_TYPE_CHAR,
> +     TOKEN_TYPE_ETHER_HEADER,
> +     TOKEN_TYPE_INT32_T,
> +     TOKEN_TYPE_IP_HEADER,
> +     TOKEN_TYPE_IP_HEADERS,
> +     TOKEN_TYPE_RTE_MBUF,
> +     TOKEN_TYPE_TCP_HEADER,
> +     TOKEN_TYPE_TCP_HEADERS,
> +     TOKEN_TYPE_UDP_HEADER,
> +     TOKEN_TYPE_UDP_HEADERS,
> +     TOKEN_TYPE_UINT32_T,
> +     TOKEN_TYPE_UINT64_T,
> +     TOKEN_TYPE_UINTPTR_T,
> +     TOKEN_TYPE_VOID,
> +
> +     TOKEN_TYPES_END,
> +};
> +
> +/* Return true if the token is a type token */
> +static bool
> +is_type_token(enum token token)
> +{
> +     return token > TOKEN_TYPES_BEGIN && token < TOKEN_TYPES_END;
> +}
> +
> +struct text_token {
> +     const char *text;
> +     unsigned int length;  /* Not size_t to fit this struct in 2 regs. */
> +     enum token token;
> +};
> +
> +#define TEXT_TOKEN_DEF(text_, token_) {                                      
>  \
> +     .text = text_,                                                        \
> +     .length = sizeof(text_) - 1,                                          \
> +     .token = token_,                                                      \
> +}
> +
> +/* Token search table, MUST BE SORTED! */
> +const struct text_token TEXT_TOKENS[] = {
> +     TEXT_TOKEN_DEF("(",                     TOKEN_PARENTHESIS_OPEN),
> +     TEXT_TOKEN_DEF(")",                     TOKEN_PARENTHESIS_CLOSE),
> +     TEXT_TOKEN_DEF("*",                     TOKEN_ASTERISK),
> +     TEXT_TOKEN_DEF(",",                     TOKEN_COMMA),
> +     TEXT_TOKEN_DEF("[",                     TOKEN_BRACKET_OPEN),
> +     TEXT_TOKEN_DEF("]",                     TOKEN_BRACKET_CLOSE),
> +     TEXT_TOKEN_DEF("char",                  TOKEN_TYPE_CHAR),
> +     TEXT_TOKEN_DEF("ether_header",
>       TOKEN_TYPE_ETHER_HEADER),
> +     TEXT_TOKEN_DEF("int32_t",               TOKEN_TYPE_INT32_T),
> +     TEXT_TOKEN_DEF("ip_header",             TOKEN_TYPE_IP_HEADER),
> +     TEXT_TOKEN_DEF("ip_headers",
>       TOKEN_TYPE_IP_HEADERS),
> +     TEXT_TOKEN_DEF("rte_ether_hdr",
>       TOKEN_TYPE_ETHER_HEADER),
> +     TEXT_TOKEN_DEF("rte_ipv4_hdr",
>       TOKEN_TYPE_IP_HEADER),
> +     TEXT_TOKEN_DEF("rte_mbuf",              TOKEN_TYPE_RTE_MBUF),
> +     TEXT_TOKEN_DEF("rte_tcp_hdr",
>       TOKEN_TYPE_TCP_HEADER),
> +     TEXT_TOKEN_DEF("rte_udp_hdr",
>       TOKEN_TYPE_UDP_HEADER),
> +     TEXT_TOKEN_DEF("struct",                TOKEN_STRUCT),
> +     TEXT_TOKEN_DEF("tcp_header",
>       TOKEN_TYPE_TCP_HEADER),
> +     TEXT_TOKEN_DEF("tcp_headers",
>       TOKEN_TYPE_TCP_HEADERS),
> +     TEXT_TOKEN_DEF("udp_header",
>       TOKEN_TYPE_UDP_HEADER),
> +     TEXT_TOKEN_DEF("udp_headers",
>       TOKEN_TYPE_UDP_HEADERS),
> +     TEXT_TOKEN_DEF("uint32_t",              TOKEN_TYPE_UINT32_T),
> +     TEXT_TOKEN_DEF("uint64_t",              TOKEN_TYPE_UINT64_T),
> +     TEXT_TOKEN_DEF("uintptr_t",             TOKEN_TYPE_UINTPTR_T),
> +     TEXT_TOKEN_DEF("void",                  TOKEN_TYPE_VOID),
> +};
> +
> +const struct text_token TEXT_TOKEN_UNRECOGNIZED = { "", 0,
> TOKEN_UNRECOGNIZED };
> +const struct text_token TEXT_TOKEN_END = { "", 0, TOKEN_END };
> +
> +/* IP header maximum possible size with options. */
> +#define IP_HEADER_MAX_SIZE 60
> +#define IP_HEADERS_MAX_SIZE (sizeof(struct rte_ether_hdr) +
> IP_HEADER_MAX_SIZE)
> +#define TCP_HEADERS_MAX_SIZE (IP_HEADERS_MAX_SIZE + sizeof(struct
> rte_tcp_hdr))
> +#define UDP_HEADERS_MAX_SIZE (IP_HEADERS_MAX_SIZE + sizeof(struct
> rte_udp_hdr))
> +
> +#define POINTER_SIZE (sizeof(char *))
> +
> +const size_t TYPE_TOKEN_SIZE[] = {
> +     [TOKEN_TYPE_CHAR]               = sizeof(char),
> +     [TOKEN_TYPE_ETHER_HEADER]       = sizeof(struct rte_ether_hdr),
> +     [TOKEN_TYPE_INT32_T]            = sizeof(int32_t),
> +     [TOKEN_TYPE_IP_HEADERS]         = IP_HEADERS_MAX_SIZE,
> +     [TOKEN_TYPE_IP_HEADER]          = sizeof(struct rte_ipv4_hdr),
> +     [TOKEN_TYPE_TCP_HEADERS]        = TCP_HEADERS_MAX_SIZE,
> +     [TOKEN_TYPE_TCP_HEADER]         = sizeof(struct rte_tcp_hdr),
> +     [TOKEN_TYPE_UDP_HEADERS]        = UDP_HEADERS_MAX_SIZE,
> +     [TOKEN_TYPE_UDP_HEADER]         = sizeof(struct rte_udp_hdr),
> +     [TOKEN_TYPE_UINT32_T]           = sizeof(uint32_t),
> +     [TOKEN_TYPE_UINT64_T]           = sizeof(uint64_t),
> +     [TOKEN_TYPE_UINTPTR_T]          = sizeof(uintptr_t),
> +};
> +
> +/*
> + * Struct rte_bpf_arg augmented with a type of pointer to it, since 
> rte_bpf_arg
> + * by itself may not contain enough information to determine its pointer 
> type.
> + */
> +struct arg_info {
> +     struct rte_bpf_arg value;
> +     enum rte_bpf_arg_type ptr_type;
> +     bool is_array;
> +};
> +
> +/* Return arg_info struct described by the specified type token. */
> +static struct arg_info
> +get_type_token_arg(enum token type)
> +{
> +     RTE_ASSERT(is_type_token(type));
> +     switch (type) {
> +     case TOKEN_TYPE_VOID:
> +             return (struct arg_info){
> +                     .value = { .type = RTE_BPF_ARG_UNDEF },
> +                     .ptr_type = RTE_BPF_ARG_PTR,
> +             };
> +     case TOKEN_TYPE_RTE_MBUF:
> +             return (struct arg_info){
> +                     .value = {
> +                             .type = RTE_BPF_ARG_RAW,
> +                             .size = sizeof(struct rte_mbuf),
> +                             .buf_size = RTE_MBUF_DEFAULT_BUF_SIZE,
> +                     },
> +                     .ptr_type = RTE_BPF_ARG_PTR_MBUF,
> +             };
> +     default:
> +             /* Should only reach here for normal sized types. */
> +             RTE_ASSERT((size_t)type < RTE_DIM(TYPE_TOKEN_SIZE) &&
> +                     TYPE_TOKEN_SIZE[type] != 0);
> +             return (struct arg_info){
> +                     .value = {
> +                             .type = RTE_BPF_ARG_RAW,
> +                             .size = TYPE_TOKEN_SIZE[type],
> +                     },
> +                     .ptr_type = RTE_BPF_ARG_PTR,
> +             };
> +     }
> +}
> +
> +
> +/* PARSING TEXT INTO TOKENS */
> +
> +/* Return true if character matches [a-zA-Z0-9_] in regex */
> +static bool
> +iswordchar(char character)
> +{
> +     return isalnum(character) || character == '_';
> +}
> +
> +/*
> + * Compare pointer to text with pointer to struct text_token to determine
> + * if text starts with the specified token.
> + */
> +static int
> +text_and_text_token_cmp(const void *text_void_ptr, const void
> *text_token_void_ptr)
> +{
> +     int result;
> +     const char * const text = text_void_ptr;
> +     const struct text_token * const text_token = text_token_void_ptr;
> +
> +     if (memchr(text, 0, text_token->length) != NULL)
> +             /* Text is shorter than the token. */
> +             return strcmp(text, text_token->text);
> +
> +     /* Cannot use strcmp because we are looking for a prefix. */
> +     result = memcmp(text, text_token->text, text_token->length);

I am puzzled here..
Why not simply strncmp(text, text_token->text, text_token->length);
?

> +
> +     /* Checking the case of a partial word match. */
> +     if (result == 0 && iswordchar(text[text_token->length - 1]) &&
> +                     iswordchar(text[text_token->length]))
> +             /* Text word is longer than the token. */
> +             result = 1;
> +
> +     return result;
> +}
> +
> +/* Advance pointed character pointer to the first non-space character. */
> +static void
> +skip_space(const char **text_ptr)
> +{
> +     while (isspace(**text_ptr))
> +             ++*text_ptr;
> +}
> +
> +/*
> + * Recognize and return a token starting text.
> + * Return TEXT_TOKEN_END if text is empty.
> + * Return TEXT_TOKEN_UNRECOGNIZED if text starts with unknown token.
> + */
> +static struct text_token
> +peek_text_token(const char *text)
> +{
> +     if (*text == '\0')
> +             return TEXT_TOKEN_END;
> +     const struct text_token * const text_token = bsearch(text, TEXT_TOKENS,
> +             RTE_DIM(TEXT_TOKENS), sizeof(TEXT_TOKENS[0]),
> +             text_and_text_token_cmp);
> +     if (text_token == NULL)
> +             return TEXT_TOKEN_UNRECOGNIZED;
> +     return *text_token;
> +}
> +
> +/*
> + * Advance pointed text starting with the specified token to the first
> + * non-space character after it.
> + * Do nothing if called for TEXT_TOKEN_END or TEXT_TOKEN_UNRECOGNIZED.
> + */
> +static void
> +consume_text_token(const char **text_ptr, struct text_token text_token)
> +{
> +     RTE_ASSERT(memcmp(*text_ptr, text_token.text, text_token.length) == 0);
> +     *text_ptr += text_token.length;
> +     skip_space(text_ptr);
> +}
> +
> +/* Return length of the word starting at `text`. */
> +static size_t
> +find_word_length(const char *text)
> +{
> +     size_t word_length = 0;
> +     while (iswordchar(text[word_length]))
> +             ++word_length;
> +     return word_length;
> +}
> +
> +/* Create a copy of the word starting text, advance to next token. */
> +static const char *
> +take_name(const char **text_ptr, struct alloc_list *alloc_list)
> +{
> +     const size_t word_length = find_word_length(*text_ptr);
> +     if (word_length == 0)
> +             /* Text does not start with a word. */
> +             return NULL;
> +
> +     /* Allocate memory for the word and add it to the alloc_list */
> +     char *word = malloc(word_length + 1);
> +     RTE_VERIFY(word != NULL);
> +     alloc_list_append(alloc_list, word);
> +
> +     /* Copy and terminate word contents. */
> +     memcpy(word, *text_ptr, word_length);
> +     word[word_length] = '\0';
> +
> +     /* Advance text pointer. */
> +     *text_ptr += word_length;
> +     skip_space(text_ptr);
> +
> +     return word;
> +}
> +
> +/* Read a number starting text, advance to next token. */
> +static int
> +take_number(size_t *number, const char **text_ptr)
> +{
> +     /* Read a word and let strtoull to decide if it's a number. */
> +     const size_t word_length = find_word_length(*text_ptr);
> +     if (word_length == 0)
> +             /* Text does not start with a word. */
> +             return -ENOENT;
> +
> +     errno = 0;
> +     char *number_end;
> +     unsigned long long long_number = strtoull(*text_ptr, &number_end, 0);
> +     if (errno > 0)
> +             return -errno;
> +     if (number_end != *text_ptr + word_length)
> +             /* Could not parse whole word. */
> +             return -EINVAL;
> +     if (long_number > SIZE_MAX)

I think stroul() will return ERANGE itself if the resulting value iss out of 
range.
So not sure that precaution (strtoull()l then check range is necessary). 

> +             return -ERANGE;
> +
> +     *number = long_number;
> +
> +     /* Advance text pointer. */
> +     *text_ptr += word_length;
> +     skip_space(text_ptr);
> +
> +     return 0;
> +}
> +
> +
> +/* PARSING DECLARATION PARTS */
> +
> +/* Change arg into a reference. */
> +static void
> +change_into_reference(struct arg_info *arg)
> +{
> +     if (RTE_BPF_ARG_PTR_TYPE(arg->value.type) != 0) {
> +             VALIDATE_BPF_LOG(WARNING,
> +                     "After taking reference to a pointer the latter "
> +                     "will be described as an opaque pointer-size blob.");
> +             RTE_ASSERT(arg->ptr_type == RTE_BPF_ARG_PTR);
> +             arg->value.size = POINTER_SIZE;
> +     }
> +     arg->value.type = arg->ptr_type;
> +     arg->ptr_type = RTE_BPF_ARG_PTR;
> +     arg->is_array = false;
> +}
> +
> +/*
> + * Recognize and consume arg starting text, advance to next token.
> + */
> +static int
> +take_arg(struct arg_info *arg, const char **text_ptr, const char *text_start)
> +{
> +     struct text_token next = peek_text_token(*text_ptr);
> +
> +     if (next.token == TOKEN_STRUCT) {
> +             consume_text_token(text_ptr, next);
> +             next = peek_text_token(*text_ptr);
> +     }
> +
> +     if (!is_type_token(next.token))
> +             RETURN_TEXT_ERROR(*text_ptr, text_start, "expect type");
> +     const enum token type = next.token;
> +     consume_text_token(text_ptr, next);
> +     next = peek_text_token(*text_ptr);
> +
> +     *arg = get_type_token_arg(type);
> +
> +     while (next.token == TOKEN_ASTERISK) {
> +             consume_text_token(text_ptr, next);
> +             next = peek_text_token(*text_ptr);
> +             change_into_reference(arg);
> +     }
> +
> +     while (next.token == TOKEN_BRACKET_OPEN) {
> +             consume_text_token(text_ptr, next);
> +
> +             /* Initialize to zero to avoid spurious compiler warnings. */
> +             size_t array_length = 0;
> +             if (take_number(&array_length, text_ptr) < 0)
> +                     RETURN_TEXT_ERROR(*text_ptr, text_start, "expect
> length");
> +             next = peek_text_token(*text_ptr);
> +
> +             if (arg->value.size != 0 &&
> +                             array_length > SIZE_MAX / arg->value.size)
> +                     RETURN_TEXT_ERROR(*text_ptr, text_start, "type too
> big");
> +
> +             if (next.token != TOKEN_BRACKET_CLOSE)
> +                     RETURN_TEXT_ERROR(*text_ptr, text_start, "expect ']'");
> +             consume_text_token(text_ptr, next);
> +             next = peek_text_token(*text_ptr);
> +
> +             change_into_reference(arg);
> +             arg->value.size *= array_length;
> +             arg->is_array = true;
> +     }
> +
> +     return 0;
> +}
> +
> +/* Fill struct rte_bpf_arg within xsym trying not to unzero the padding. */
> +static void
> +fill_xsym_arg(struct rte_bpf_arg *target, struct rte_bpf_arg source)
> +{
> +     /* Copy fields individually to try and prevent copying the padding. */
> +     target->type = source.type;
> +     target->size = source.size;
> +     target->buf_size = source.buf_size;
> +}
> +
> +/* Build and return struct rte_bpf_arg of type RTE_BPF_XTYPE_VAR */
> +static int
> +fill_var_xsym(struct rte_bpf_xsym *xsym, struct arg_info arg, const char 
> *name,
> +     struct alloc_list *alloc_list)
> +{
> +     /* Variables are passed by reference, except for arrays. */
> +     if (!arg.is_array) {
> +             if (RTE_BPF_ARG_PTR_TYPE(arg.value.type) != 0)
> +                     VALIDATE_BPF_LOG(WARNING,
> +                             "External pointers may not work as expected "
> +                             "because all external variables are passed by "
> +                             "reference but there is currently no way to "
> +                             "describe double pointer to the validator.");
> +             change_into_reference(&arg);
> +     }
> +
> +     /* Allocate something to assign to a val pointer. */
> +     void * const val = calloc(1, RTE_MAX(1u, arg.value.size));
> +     if (val == NULL) {
> +             VALIDATE_BPF_LOG(ERR, "could not allocate enough memory");
> +             return -1;
> +     }
> +     alloc_list_append(alloc_list, val);
> +
> +     /* Need all padding and unused fields to be zero-filled. */
> +     memset(xsym, 0, sizeof(*xsym));
> +     xsym->name = name;
> +     xsym->type = RTE_BPF_XTYPE_VAR;
> +     xsym->var.val = val;
> +     fill_xsym_arg(&xsym->var.desc, arg.value);
> +
> +     return 0;
> +}
> +
> +/* Build and return struct rte_bpf_arg of type RTE_BPF_XTYPE_FUNC */
> +static void
> +fill_func_xsym(struct rte_bpf_xsym *xsym, struct arg_info arg, const char 
> *name)
> +{
> +     /* Need all padding and unused fields to be zero-filled. */
> +     memset(xsym, 0, sizeof(*xsym));
> +     xsym->name = name;
> +     xsym->type = RTE_BPF_XTYPE_FUNC;
> +     xsym->func.val = &dummy_function;
> +     fill_xsym_arg(&xsym->func.ret, arg.value);
> +}
> +
> +/*
> + * Parse and store function arguments, advance to next token after ')'.
> + * Value of *text_ptr should point to the next token after '('.
> + */
> +static int
> +take_func_xsym_args(struct rte_bpf_xsym *xsym, const char **text_ptr,
> +     const char *text_start)
> +{
> +     struct arg_info arg;
> +     struct text_token delimiter = peek_text_token(*text_ptr);
> +
> +     while (delimiter.token != TOKEN_PARENTHESIS_CLOSE) {
> +             if (xsym->func.nb_args == EBPF_FUNC_MAX_ARGS)
> +                     RETURN_TEXT_ERROR(*text_ptr, text_start,
> +                             "too many arguments, maximum %d allowed",
> +                             EBPF_FUNC_MAX_ARGS);
> +
> +             if (take_arg(&arg, text_ptr, text_start) < 0)
> +                     return -1;
> +             if (arg.value.type == RTE_BPF_ARG_UNDEF &&
> +                             xsym->func.nb_args != 0)
> +                     RETURN_TEXT_ERROR(*text_ptr, text_start,
> +                             "arguments of type void are not allowed");
> +             fill_xsym_arg(&xsym->func.args[xsym->func.nb_args++],
> +                     arg.value);
> +
> +             delimiter = peek_text_token(*text_ptr);
> +             switch (delimiter.token) {
> +             case TOKEN_COMMA:
> +                     consume_text_token(text_ptr, delimiter);
> +                     continue;
> +             case TOKEN_PARENTHESIS_CLOSE:
> +                     break;
> +             default:
> +                     RETURN_TEXT_ERROR(*text_ptr, text_start,
> +                             "expect ')' or ','");
> +             }
> +     }
> +     consume_text_token(text_ptr, delimiter);
> +
> +     /* Special case of single void argument. */
> +     if (xsym->func.nb_args == 1 &&
> +                     xsym->func.args[0].type == RTE_BPF_ARG_UNDEF) {
> +             xsym->func.nb_args = 0;
> +             /* Need all padding and unused fields to be zero-filled. */
> +             fill_xsym_arg(&xsym->func.args[0], (struct rte_bpf_arg){});
> +     }
> +
> +     return 0;
> +}
> +
> +/* Make sure text has ended. */
> +static int
> +ensure_end(const char *text, const char *text_start)
> +{
> +     if (peek_text_token(text).token != TOKEN_END)
> +             RETURN_TEXT_ERROR(text, text_start, "trailing garbage");
> +     return 0;
> +}
> +
> +/* Parse and store xsym, advance to next token. */
> +static int
> +take_xsym(struct rte_bpf_xsym *xsym, const char **text_ptr,
> +     const char *text_start, struct alloc_list *alloc_list)
> +{
> +     struct arg_info arg;
> +
> +     if (take_arg(&arg, text_ptr, text_start) < 0)
> +             return -1;
> +
> +     const char * const name = take_name(text_ptr, alloc_list);
> +     if (name == NULL)
> +             RETURN_TEXT_ERROR(*text_ptr, text_start, "expect name");
> +
> +     const struct text_token next = peek_text_token(*text_ptr);
> +     switch (next.token) {
> +     case TOKEN_END:
> +             if (fill_var_xsym(xsym, arg, name, alloc_list) < 0)
> +                     return -1;
> +             break;
> +     case TOKEN_PARENTHESIS_OPEN:
> +             consume_text_token(text_ptr, next);
> +             fill_func_xsym(xsym, arg, name);
> +             if (take_func_xsym_args(xsym, text_ptr, text_start) < 0)
> +                     return -1;
> +             break;
> +     default:
> +             RETURN_TEXT_ERROR(*text_ptr, text_start,
> +                     "expect '(' or text end");
> +     }
> +     return 0;
> +}
> +
> +
> +/* PUBLIC FUNCTIONS */
> +
> +void
> +print_supported_types(void)
> +{
> +     printf("TYPE: [struct] BASIC_TYPE [*]... [[N]]...\n");
> +     printf("BASIC_TYPE: one of\n");
> +     for (int tti = 0; tti != RTE_DIM(TEXT_TOKENS); ++tti) {
> +             if (is_type_token(TEXT_TOKENS[tti].token))
> +                     printf("\t%s\n", TEXT_TOKENS[tti].text);
> +     }
> +}
> +
> +int
> +parse_arg(struct rte_bpf_arg *arg, const char *text)
> +{
> +     struct arg_info arg_info;
> +     const char * const text_start = text;
> +     skip_space(&text);
> +     if (take_arg(&arg_info, &text, text_start) < 0)
> +             return -1;
> +     if (ensure_end(text, text_start) < 0)
> +             return -1;
> +     if (arg_info.ptr_type != RTE_BPF_ARG_PTR)
> +             VALIDATE_BPF_LOG(WARNING,
> +                     "`%s` has a special pointer type which was left unused; 
> "
> +                     "argument was set to an opaque blob.",
> +                     text_start);
> +     *arg = arg_info.value;
> +     return 0;
> +}
> +
> +int
> +parse_xsym(struct rte_bpf_xsym *xsym, const char *text,
> +     struct alloc_list *alloc_list)
> +{
> +     const char * const text_start = text;
> +     skip_space(&text);
> +     if (take_xsym(xsym, &text, text_start, alloc_list) < 0)
> +             return -1;
> +     if (ensure_end(text, text_start) < 0)
> +             return -1;
> +     return 0;
> +}
> +
> +void
> +adjust_arg_buf_size(struct rte_bpf_arg *arg, size_t mbuf_buf_size)
> +{
> +     if (arg->buf_size != 0)
> +             /* Non-zero buf_size indicates mbuf or a pointer to it. */
> +             arg->buf_size = mbuf_buf_size;
> +}
> +
> +void
> +adjust_xsym_buf_size(struct rte_bpf_xsym *xsym, size_t mbuf_buf_size)
> +{
> +     switch (xsym->type) {
> +     case RTE_BPF_XTYPE_FUNC:
> +             for (uint32_t argi = 0; argi != xsym->func.nb_args; ++argi)
> +                     adjust_arg_buf_size(&xsym->func.args[argi],
> +                             mbuf_buf_size);
> +             adjust_arg_buf_size(&xsym->func.ret, mbuf_buf_size);
> +             break;
> +     case RTE_BPF_XTYPE_VAR:
> +             adjust_arg_buf_size(&xsym->var.desc, mbuf_buf_size);
> +             break;
> +     default:
> +             rte_panic("Unexpected xsym type %d\n", xsym->type);
> +     }
> +}

Reply via email to