Re: [PATCH bpf-next v4 1/2] selftests/bpf: implement setting global variables in veristat

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Fri, Feb 21, 2025 at 2:33 PM Mykyta Yatsenko
<mykyta.yatsenko5@xxxxxxxxx> wrote:
>
> From: Mykyta Yatsenko <yatsenko@xxxxxxxx>
>
> To better verify some complex BPF programs we'd like to preset global
> variables.
> This patch introduces CLI argument `--set-global-vars` or `-G` to
> veristat, that allows presetting values to global variables defined
> in BPF program. For example:
>
> prog.c:
> ```
> enum Enum { ELEMENT1 = 0, ELEMENT2 = 5 };
> const volatile __s64 a = 5;
> const volatile __u8 b = 5;
> const volatile enum Enum c = ELEMENT2;
> const volatile bool d = false;
>
> char arr[4] = {0};
>
> SEC("tp_btf/sched_switch")
> int BPF_PROG(...)
> {
>         bpf_printk("%c\n", arr[a]);
>         bpf_printk("%c\n", arr[b]);
>         bpf_printk("%c\n", arr[c]);
>         bpf_printk("%c\n", arr[d]);
>         return 0;
> }
> ```
> By default verification of the program fails:
> ```
> ./veristat prog.bpf.o
> ```
> By presetting global variables, we can make verification pass:
> ```
> ./veristat wq.bpf.o  -G "a = 0" -G "b = 1" -G "c = 2" -G "d = 3"
> ```
>
> Signed-off-by: Mykyta Yatsenko <yatsenko@xxxxxxxx>
> ---
>  tools/testing/selftests/bpf/veristat.c | 290 ++++++++++++++++++++++++-
>  1 file changed, 289 insertions(+), 1 deletion(-)
>

Just a few minor things below, but overall looks great!

> diff --git a/tools/testing/selftests/bpf/veristat.c b/tools/testing/selftests/bpf/veristat.c
> index 06af5029885b..1e1bc0dfa50a 100644
> --- a/tools/testing/selftests/bpf/veristat.c
> +++ b/tools/testing/selftests/bpf/veristat.c
> @@ -154,6 +154,16 @@ struct filter {
>         bool abs;
>  };
>
> +struct var_preset {
> +       char *name;
> +       enum { INTEGRAL, NAME } type;
> +       union {
> +               long long ivalue;
> +               char *svalue;
> +       };
> +       bool applied;
> +};
> +
>  static struct env {
>         char **filenames;
>         int filename_cnt;
> @@ -195,6 +205,8 @@ static struct env {
>         int progs_processed;
>         int progs_skipped;
>         int top_src_lines;
> +       struct var_preset *presets;
> +       int npresets;
>  } env;
>
>  static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
> @@ -246,12 +258,15 @@ static const struct argp_option opts[] = {
>         { "test-reg-invariants", 'r', NULL, 0,
>           "Force BPF verifier failure on register invariant violation (BPF_F_TEST_REG_INVARIANTS program flag)" },
>         { "top-src-lines", 'S', "N", 0, "Emit N most frequent source code lines" },
> +       { "set-global-vars", 'G', "GLOBALS", 0, "Set global variables provided in the expression, for example \"var1 = 1\"" },

GLOBAL (singular)

>         {},
>  };
>

[...]

> +static int append_var_preset(struct var_preset **presets, int *cnt, const char *expr)
> +{
> +       void *tmp;
> +       struct var_preset *cur;
> +       char var[256], val[256];
> +
> +       tmp = realloc(*presets, (*cnt + 1) * sizeof(**presets));
> +       if (!tmp)
> +               return -ENOMEM;
> +       *presets = tmp;
> +       cur = &(*presets)[*cnt];
> +       cur->applied = false;
> +
> +       if (sscanf(expr, "%s = %s\n", var, val) != 2) {
> +               fprintf(stderr, "Could not parse expression %s\n", expr);
> +               return -EINVAL;
> +       }
> +
> +       if (isalpha(*val)) {

technically enum could be _WHATEVER, underscore is legal character

> +               cur->svalue = strdup(val);
> +               cur->type = NAME;

total nit, but NAME confused me a bit, as I assumed that the name of
the global variable. It's really just for enums, right? So ENUM or
ENUMERATOR would make sense and be more precise?

> +       } else if (*val == '-' || isdigit(*val)) {

instead of doing this detection based on characters, why not try to
parse a number (and I'd use sscanf("%lli") which will handle 1234 and
0x1234 transparently)? And you can use %n to check that all characters
were parsed (i.e., you have exact match)

it's probably fine for starters, but it kind of sucks that 0x1234
isn't supported

> +               long long value;
> +
> +               errno = 0;
> +               value = strtoll(val, NULL, 0);
> +               if (errno == ERANGE) {
> +                       errno = 0;
> +                       value = strtoull(val, NULL, 0);
> +               }
> +               cur->ivalue = value;
> +               cur->type = INTEGRAL;
> +               if (errno) {
> +                       fprintf(stderr, "Could not parse integer value %s\n", val);
> +                       return -EINVAL;
> +               }
> +       } else {
> +               fprintf(stderr, "Could not parse value %s\n", val);
> +               return -EINVAL;
> +       }
> +       cur->name = strdup(var);

check for NULL and error out? (no need for logging message, it's rare
to get ENOMEM)

pw-bot: cr

> +       (*cnt)++;
> +       return 0;
> +}
> +

[...]

> +       /* Check if value fits into the target variable size */
> +       if  (sinfo->size < sizeof(value)) {
> +               bool is_signed = is_signed_type(base_type);
> +               __u32 unsigned_bits = sinfo->size * 8 - (is_signed ? 1 : 0);
> +               long long max_val = 1ll << unsigned_bits;
> +
> +               if (value >= max_val || value < -max_val) {
> +                       fprintf(stderr,
> +                               "Variable %s value %lld is out of range [%lld; %lld]\n",
> +                               btf__name_by_offset(btf, t->name_off), value,
> +                               is_signed ? -max_val : 0, max_val - 1);
> +                       return -EINVAL;
> +               }
> +       }
> +
> +       ptr = (void *)bpf_map__initial_value(map, &size);

it already returns void *, no need to cast

> +       if (!ptr || (sinfo->offset + sinfo->size > size))

nit: unnecessary ()

> +               return -EINVAL;
> +
> +       if (__BYTE_ORDER == __LITTLE_ENDIAN) {
> +               memcpy(ptr + sinfo->offset, &value, sinfo->size);
> +       } else if (__BYTE_ORDER == __BIG_ENDIAN) {

either this if condition is superficial, or we'd need to add another
else with error. let's just

} else { /* __BYTE_ORDER == __BIG_ENDIAN */
   memcpy()
}

?

> +               __u8 src_offset = sizeof(value) - sinfo->size;
> +
> +               memcpy(ptr + sinfo->offset, (void *)&value + src_offset, sinfo->size);
> +       }
> +       return 0;
> +}
> +

[...]

>  static int process_obj(const char *filename)
>  {
>         const char *base_filename = basename(strdupa(filename));
> @@ -1341,6 +1612,11 @@ static int process_obj(const char *filename)
>         if (prog_cnt == 1) {
>                 prog = bpf_object__next_program(obj, NULL);
>                 bpf_program__set_autoload(prog, true);
> +               err = set_global_vars(obj, env.presets, env.npresets);
> +               if (err) {
> +                       fprintf(stderr, "Failed to set global variables\n");

emit error code to help with debugging in the future? same below

> +                       goto cleanup;
> +               }
>                 process_prog(filename, obj, prog);
>                 goto cleanup;
>         }

[...]





[Index of Archives]     [Linux Samsung SoC]     [Linux Rockchip SoC]     [Linux Actions SoC]     [Linux for Synopsys ARC Processors]     [Linux NFS]     [Linux NILFS]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]


  Powered by Linux