> 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 *)®ister_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); > + } > +}

