Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

Merge branch 'selftests-bpf-implement-setting-global-variables-in-veristat'

Mykyta Yatsenko says:

====================
selftests/bpf: implement setting global variables in veristat

From: Mykyta Yatsenko <yatsenko@meta.com>

To better verify some complex BPF programs by veristat, it would be useful
to preset global variables. This patch set implements this functionality
and introduces tests for veristat.

v4->v5
* Rework parsing to use sscanf for integers
* Addressing nits

v3->v4:
* Fixing bug in set_global_var introduced by refactoring in previous patch set
* Addressed nits from Eduard

v2->v3:
* Reworked parsing of the presets, using sscanf to split into variable and
value, but still use strtoll/strtoull to support range checks when parsing
integers
* Fix test failures for no_alu32 & cpuv4 by checking if veristat binary is in
parent folder
* Introduce __CHECK_STR macro for simplifying checks in test
* Modify tests into sub-tests
====================

Link: https://patch.msgid.link/20250225163101.121043-1-mykyta.yatsenko5@gmail.com
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>

authored by

Andrii Nakryiko and committed by
Alexei Starovoitov
b0f2ee60 0ba0ef01

+495 -1
+1
tools/testing/selftests/bpf/Makefile
··· 688 688 $(TRUNNER_EXTRA_OBJS) $$(BPFOBJ) \ 689 689 $(RESOLVE_BTFIDS) \ 690 690 $(TRUNNER_BPFTOOL) \ 691 + $(OUTPUT)/veristat \ 691 692 | $(TRUNNER_BINARY)-extras 692 693 $$(call msg,BINARY,,$$@) 693 694 $(Q)$$(CC) $$(CFLAGS) $$(filter %.a %.o,$$^) $$(LDLIBS) $$(LDFLAGS) -o $$@
+139
tools/testing/selftests/bpf/prog_tests/test_veristat.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */ 3 + #include <test_progs.h> 4 + #include <string.h> 5 + #include <stdio.h> 6 + 7 + #define __CHECK_STR(str, name) \ 8 + do { \ 9 + if (!ASSERT_HAS_SUBSTR(fix->output, (str), (name))) \ 10 + goto out; \ 11 + } while (0) 12 + 13 + struct fixture { 14 + char tmpfile[80]; 15 + int fd; 16 + char *output; 17 + size_t sz; 18 + char veristat[80]; 19 + }; 20 + 21 + static struct fixture *init_fixture(void) 22 + { 23 + struct fixture *fix = malloc(sizeof(struct fixture)); 24 + 25 + /* for no_alu32 and cpuv4 veristat is in parent folder */ 26 + if (access("./veristat", F_OK) == 0) 27 + strcpy(fix->veristat, "./veristat"); 28 + else if (access("../veristat", F_OK) == 0) 29 + strcpy(fix->veristat, "../veristat"); 30 + else 31 + PRINT_FAIL("Can't find veristat binary"); 32 + 33 + snprintf(fix->tmpfile, sizeof(fix->tmpfile), "/tmp/test_veristat.XXXXXX"); 34 + fix->fd = mkstemp(fix->tmpfile); 35 + fix->sz = 1000000; 36 + fix->output = malloc(fix->sz); 37 + return fix; 38 + } 39 + 40 + static void teardown_fixture(struct fixture *fix) 41 + { 42 + free(fix->output); 43 + close(fix->fd); 44 + remove(fix->tmpfile); 45 + free(fix); 46 + } 47 + 48 + static void test_set_global_vars_succeeds(void) 49 + { 50 + struct fixture *fix = init_fixture(); 51 + 52 + SYS(out, 53 + "%s set_global_vars.bpf.o"\ 54 + " -G \"var_s64 = 0xf000000000000001\" "\ 55 + " -G \"var_u64 = 0xfedcba9876543210\" "\ 56 + " -G \"var_s32 = -0x80000000\" "\ 57 + " -G \"var_u32 = 0x76543210\" "\ 58 + " -G \"var_s16 = -32768\" "\ 59 + " -G \"var_u16 = 60652\" "\ 60 + " -G \"var_s8 = -128\" "\ 61 + " -G \"var_u8 = 255\" "\ 62 + " -G \"var_ea = EA2\" "\ 63 + " -G \"var_eb = EB2\" "\ 64 + " -G \"var_ec = EC2\" "\ 65 + " -G \"var_b = 1\" "\ 66 + "-vl2 > %s", fix->veristat, fix->tmpfile); 67 + 68 + read(fix->fd, fix->output, fix->sz); 69 + __CHECK_STR("_w=0xf000000000000001 ", "var_s64 = 0xf000000000000001"); 70 + __CHECK_STR("_w=0xfedcba9876543210 ", "var_u64 = 0xfedcba9876543210"); 71 + __CHECK_STR("_w=0x80000000 ", "var_s32 = -0x80000000"); 72 + __CHECK_STR("_w=0x76543210 ", "var_u32 = 0x76543210"); 73 + __CHECK_STR("_w=0x8000 ", "var_s16 = -32768"); 74 + __CHECK_STR("_w=0xecec ", "var_u16 = 60652"); 75 + __CHECK_STR("_w=128 ", "var_s8 = -128"); 76 + __CHECK_STR("_w=255 ", "var_u8 = 255"); 77 + __CHECK_STR("_w=11 ", "var_ea = EA2"); 78 + __CHECK_STR("_w=12 ", "var_eb = EB2"); 79 + __CHECK_STR("_w=13 ", "var_ec = EC2"); 80 + __CHECK_STR("_w=1 ", "var_b = 1"); 81 + 82 + out: 83 + teardown_fixture(fix); 84 + } 85 + 86 + static void test_set_global_vars_from_file_succeeds(void) 87 + { 88 + struct fixture *fix = init_fixture(); 89 + char input_file[80]; 90 + const char *vars = "var_s16 = -32768\nvar_u16 = 60652"; 91 + int fd; 92 + 93 + snprintf(input_file, sizeof(input_file), "/tmp/veristat_input.XXXXXX"); 94 + fd = mkstemp(input_file); 95 + if (!ASSERT_GE(fd, 0, "valid fd")) 96 + goto out; 97 + 98 + write(fd, vars, strlen(vars)); 99 + syncfs(fd); 100 + SYS(out, "%s set_global_vars.bpf.o -G \"@%s\" -vl2 > %s", 101 + fix->veristat, input_file, fix->tmpfile); 102 + read(fix->fd, fix->output, fix->sz); 103 + __CHECK_STR("_w=0x8000 ", "var_s16 = -32768"); 104 + __CHECK_STR("_w=0xecec ", "var_u16 = 60652"); 105 + 106 + out: 107 + close(fd); 108 + remove(input_file); 109 + teardown_fixture(fix); 110 + } 111 + 112 + static void test_set_global_vars_out_of_range(void) 113 + { 114 + struct fixture *fix = init_fixture(); 115 + 116 + SYS_FAIL(out, 117 + "%s set_global_vars.bpf.o -G \"var_s32 = 2147483648\" -vl2 2> %s", 118 + fix->veristat, fix->tmpfile); 119 + 120 + read(fix->fd, fix->output, fix->sz); 121 + __CHECK_STR("is out of range [-2147483648; 2147483647]", "out of range"); 122 + 123 + out: 124 + teardown_fixture(fix); 125 + } 126 + 127 + void test_veristat(void) 128 + { 129 + if (test__start_subtest("set_global_vars_succeeds")) 130 + test_set_global_vars_succeeds(); 131 + 132 + if (test__start_subtest("set_global_vars_out_of_range")) 133 + test_set_global_vars_out_of_range(); 134 + 135 + if (test__start_subtest("set_global_vars_from_file_succeeds")) 136 + test_set_global_vars_from_file_succeeds(); 137 + } 138 + 139 + #undef __CHECK_STR
+47
tools/testing/selftests/bpf/progs/set_global_vars.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */ 3 + #include "bpf_experimental.h" 4 + #include <bpf/bpf_helpers.h> 5 + #include "bpf_misc.h" 6 + #include <stdbool.h> 7 + 8 + char _license[] SEC("license") = "GPL"; 9 + 10 + enum Enum { EA1 = 0, EA2 = 11 }; 11 + enum Enumu64 {EB1 = 0llu, EB2 = 12llu }; 12 + enum Enums64 { EC1 = 0ll, EC2 = 13ll }; 13 + 14 + const volatile __s64 var_s64 = -1; 15 + const volatile __u64 var_u64 = 0; 16 + const volatile __s32 var_s32 = -1; 17 + const volatile __u32 var_u32 = 0; 18 + const volatile __s16 var_s16 = -1; 19 + const volatile __u16 var_u16 = 0; 20 + const volatile __s8 var_s8 = -1; 21 + const volatile __u8 var_u8 = 0; 22 + const volatile enum Enum var_ea = EA1; 23 + const volatile enum Enumu64 var_eb = EB1; 24 + const volatile enum Enums64 var_ec = EC1; 25 + const volatile bool var_b = false; 26 + 27 + char arr[4] = {0}; 28 + 29 + SEC("socket") 30 + int test_set_globals(void *ctx) 31 + { 32 + volatile __s8 a; 33 + 34 + a = var_s64; 35 + a = var_u64; 36 + a = var_s32; 37 + a = var_u32; 38 + a = var_s16; 39 + a = var_u16; 40 + a = var_s8; 41 + a = var_u8; 42 + a = var_ea; 43 + a = var_eb; 44 + a = var_ec; 45 + a = var_b; 46 + return a; 47 + }
+8
tools/testing/selftests/bpf/test_progs.h
··· 427 427 goto goto_label; \ 428 428 }) 429 429 430 + #define SYS_FAIL(goto_label, fmt, ...) \ 431 + ({ \ 432 + char cmd[1024]; \ 433 + snprintf(cmd, sizeof(cmd), fmt, ##__VA_ARGS__); \ 434 + if (!ASSERT_NEQ(0, system(cmd), cmd)) \ 435 + goto goto_label; \ 436 + }) 437 + 430 438 #define ALL_TO_DEV_NULL " >/dev/null 2>&1" 431 439 432 440 #define SYS_NOFAIL(fmt, ...) \
+300 -1
tools/testing/selftests/bpf/veristat.c
··· 3 3 #define _GNU_SOURCE 4 4 #include <argp.h> 5 5 #include <libgen.h> 6 + #include <ctype.h> 6 7 #include <string.h> 7 8 #include <stdlib.h> 8 9 #include <sched.h> ··· 155 154 bool abs; 156 155 }; 157 156 157 + struct var_preset { 158 + char *name; 159 + enum { INTEGRAL, ENUMERATOR } type; 160 + union { 161 + long long ivalue; 162 + char *svalue; 163 + }; 164 + bool applied; 165 + }; 166 + 158 167 static struct env { 159 168 char **filenames; 160 169 int filename_cnt; ··· 206 195 int progs_processed; 207 196 int progs_skipped; 208 197 int top_src_lines; 198 + struct var_preset *presets; 199 + int npresets; 209 200 } env; 210 201 211 202 static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args) ··· 259 246 { "test-reg-invariants", 'r', NULL, 0, 260 247 "Force BPF verifier failure on register invariant violation (BPF_F_TEST_REG_INVARIANTS program flag)" }, 261 248 { "top-src-lines", 'S', "N", 0, "Emit N most frequent source code lines" }, 249 + { "set-global-vars", 'G', "GLOBAL", 0, "Set global variables provided in the expression, for example \"var1 = 1\"" }, 262 250 {}, 263 251 }; 264 252 265 253 static int parse_stats(const char *stats_str, struct stat_specs *specs); 266 254 static int append_filter(struct filter **filters, int *cnt, const char *str); 267 255 static int append_filter_file(const char *path); 256 + static int append_var_preset(struct var_preset **presets, int *cnt, const char *expr); 257 + static int append_var_preset_file(const char *filename); 268 258 269 259 static error_t parse_arg(int key, char *arg, struct argp_state *state) 270 260 { ··· 369 353 argp_usage(state); 370 354 } 371 355 break; 356 + case 'G': { 357 + if (arg[0] == '@') 358 + err = append_var_preset_file(arg + 1); 359 + else 360 + err = append_var_preset(&env.presets, &env.npresets, arg); 361 + if (err) { 362 + fprintf(stderr, "Failed to parse global variable presets: %s\n", arg); 363 + return err; 364 + } 365 + break; 366 + } 372 367 case ARGP_KEY_ARG: 373 368 tmp = realloc(env.filenames, (env.filename_cnt + 1) * sizeof(*env.filenames)); 374 369 if (!tmp) ··· 659 632 f = fopen(path, "r"); 660 633 if (!f) { 661 634 err = -errno; 662 - fprintf(stderr, "Failed to open filters in '%s': %d\n", path, err); 635 + fprintf(stderr, "Failed to open filters in '%s': %s\n", path, strerror(err)); 663 636 return err; 664 637 } 665 638 ··· 1319 1292 return 0; 1320 1293 }; 1321 1294 1295 + static int append_var_preset(struct var_preset **presets, int *cnt, const char *expr) 1296 + { 1297 + void *tmp; 1298 + struct var_preset *cur; 1299 + char var[256], val[256], *val_end; 1300 + long long value; 1301 + int n; 1302 + 1303 + tmp = realloc(*presets, (*cnt + 1) * sizeof(**presets)); 1304 + if (!tmp) 1305 + return -ENOMEM; 1306 + *presets = tmp; 1307 + cur = &(*presets)[*cnt]; 1308 + memset(cur, 0, sizeof(*cur)); 1309 + (*cnt)++; 1310 + 1311 + if (sscanf(expr, "%s = %s %n", var, val, &n) != 2 || n != strlen(expr)) { 1312 + fprintf(stderr, "Failed to parse expression '%s'\n", expr); 1313 + return -EINVAL; 1314 + } 1315 + 1316 + if (val[0] == '-' || isdigit(val[0])) { 1317 + /* must be a number */ 1318 + errno = 0; 1319 + value = strtoll(val, &val_end, 0); 1320 + if (errno == ERANGE) { 1321 + errno = 0; 1322 + value = strtoull(val, &val_end, 0); 1323 + } 1324 + if (errno || *val_end != '\0') { 1325 + fprintf(stderr, "Failed to parse value '%s'\n", val); 1326 + return -EINVAL; 1327 + } 1328 + cur->ivalue = value; 1329 + cur->type = INTEGRAL; 1330 + } else { 1331 + /* if not a number, consider it enum value */ 1332 + cur->svalue = strdup(val); 1333 + if (!cur->svalue) 1334 + return -ENOMEM; 1335 + cur->type = ENUMERATOR; 1336 + } 1337 + 1338 + cur->name = strdup(var); 1339 + if (!cur->name) 1340 + return -ENOMEM; 1341 + 1342 + return 0; 1343 + } 1344 + 1345 + static int append_var_preset_file(const char *filename) 1346 + { 1347 + char buf[1024]; 1348 + FILE *f; 1349 + int err = 0; 1350 + 1351 + f = fopen(filename, "rt"); 1352 + if (!f) { 1353 + err = -errno; 1354 + fprintf(stderr, "Failed to open presets in '%s': %s\n", filename, strerror(err)); 1355 + return -EINVAL; 1356 + } 1357 + 1358 + while (fscanf(f, " %1023[^\n]\n", buf) == 1) { 1359 + if (buf[0] == '\0' || buf[0] == '#') 1360 + continue; 1361 + 1362 + err = append_var_preset(&env.presets, &env.npresets, buf); 1363 + if (err) 1364 + goto cleanup; 1365 + } 1366 + 1367 + cleanup: 1368 + fclose(f); 1369 + return err; 1370 + } 1371 + 1372 + static bool is_signed_type(const struct btf_type *t) 1373 + { 1374 + if (btf_is_int(t)) 1375 + return btf_int_encoding(t) & BTF_INT_SIGNED; 1376 + if (btf_is_any_enum(t)) 1377 + return btf_kflag(t); 1378 + return true; 1379 + } 1380 + 1381 + static int enum_value_from_name(const struct btf *btf, const struct btf_type *t, 1382 + const char *evalue, long long *retval) 1383 + { 1384 + if (btf_is_enum(t)) { 1385 + struct btf_enum *e = btf_enum(t); 1386 + int i, n = btf_vlen(t); 1387 + 1388 + for (i = 0; i < n; ++i, ++e) { 1389 + const char *cur_name = btf__name_by_offset(btf, e->name_off); 1390 + 1391 + if (strcmp(cur_name, evalue) == 0) { 1392 + *retval = e->val; 1393 + return 0; 1394 + } 1395 + } 1396 + } else if (btf_is_enum64(t)) { 1397 + struct btf_enum64 *e = btf_enum64(t); 1398 + int i, n = btf_vlen(t); 1399 + 1400 + for (i = 0; i < n; ++i, ++e) { 1401 + const char *cur_name = btf__name_by_offset(btf, e->name_off); 1402 + __u64 value = btf_enum64_value(e); 1403 + 1404 + if (strcmp(cur_name, evalue) == 0) { 1405 + *retval = value; 1406 + return 0; 1407 + } 1408 + } 1409 + } 1410 + return -EINVAL; 1411 + } 1412 + 1413 + static bool is_preset_supported(const struct btf_type *t) 1414 + { 1415 + return btf_is_int(t) || btf_is_enum(t) || btf_is_enum64(t); 1416 + } 1417 + 1418 + static int set_global_var(struct bpf_object *obj, struct btf *btf, const struct btf_type *t, 1419 + struct bpf_map *map, struct btf_var_secinfo *sinfo, 1420 + struct var_preset *preset) 1421 + { 1422 + const struct btf_type *base_type; 1423 + void *ptr; 1424 + long long value = preset->ivalue; 1425 + size_t size; 1426 + 1427 + base_type = btf__type_by_id(btf, btf__resolve_type(btf, t->type)); 1428 + if (!base_type) { 1429 + fprintf(stderr, "Failed to resolve type %d\n", t->type); 1430 + return -EINVAL; 1431 + } 1432 + if (!is_preset_supported(base_type)) { 1433 + fprintf(stderr, "Setting value for type %s is not supported\n", 1434 + btf__name_by_offset(btf, base_type->name_off)); 1435 + return -EINVAL; 1436 + } 1437 + 1438 + if (preset->type == ENUMERATOR) { 1439 + if (btf_is_any_enum(base_type)) { 1440 + if (enum_value_from_name(btf, base_type, preset->svalue, &value)) { 1441 + fprintf(stderr, 1442 + "Failed to find integer value for enum element %s\n", 1443 + preset->svalue); 1444 + return -EINVAL; 1445 + } 1446 + } else { 1447 + fprintf(stderr, "Value %s is not supported for type %s\n", 1448 + preset->svalue, btf__name_by_offset(btf, base_type->name_off)); 1449 + return -EINVAL; 1450 + } 1451 + } 1452 + 1453 + /* Check if value fits into the target variable size */ 1454 + if (sinfo->size < sizeof(value)) { 1455 + bool is_signed = is_signed_type(base_type); 1456 + __u32 unsigned_bits = sinfo->size * 8 - (is_signed ? 1 : 0); 1457 + long long max_val = 1ll << unsigned_bits; 1458 + 1459 + if (value >= max_val || value < -max_val) { 1460 + fprintf(stderr, 1461 + "Variable %s value %lld is out of range [%lld; %lld]\n", 1462 + btf__name_by_offset(btf, t->name_off), value, 1463 + is_signed ? -max_val : 0, max_val - 1); 1464 + return -EINVAL; 1465 + } 1466 + } 1467 + 1468 + ptr = bpf_map__initial_value(map, &size); 1469 + if (!ptr || sinfo->offset + sinfo->size > size) 1470 + return -EINVAL; 1471 + 1472 + if (__BYTE_ORDER == __LITTLE_ENDIAN) { 1473 + memcpy(ptr + sinfo->offset, &value, sinfo->size); 1474 + } else { /* __BYTE_ORDER == __BIG_ENDIAN */ 1475 + __u8 src_offset = sizeof(value) - sinfo->size; 1476 + 1477 + memcpy(ptr + sinfo->offset, (void *)&value + src_offset, sinfo->size); 1478 + } 1479 + return 0; 1480 + } 1481 + 1482 + static int set_global_vars(struct bpf_object *obj, struct var_preset *presets, int npresets) 1483 + { 1484 + struct btf_var_secinfo *sinfo; 1485 + const char *sec_name; 1486 + const struct btf_type *t; 1487 + struct bpf_map *map; 1488 + struct btf *btf; 1489 + int i, j, k, n, cnt, err = 0; 1490 + 1491 + if (npresets == 0) 1492 + return 0; 1493 + 1494 + btf = bpf_object__btf(obj); 1495 + if (!btf) 1496 + return -EINVAL; 1497 + 1498 + cnt = btf__type_cnt(btf); 1499 + for (i = 1; i != cnt; ++i) { 1500 + t = btf__type_by_id(btf, i); 1501 + 1502 + if (!btf_is_datasec(t)) 1503 + continue; 1504 + 1505 + sinfo = btf_var_secinfos(t); 1506 + sec_name = btf__name_by_offset(btf, t->name_off); 1507 + map = bpf_object__find_map_by_name(obj, sec_name); 1508 + if (!map) 1509 + continue; 1510 + 1511 + n = btf_vlen(t); 1512 + for (j = 0; j < n; ++j, ++sinfo) { 1513 + const struct btf_type *var_type = btf__type_by_id(btf, sinfo->type); 1514 + const char *var_name; 1515 + 1516 + if (!btf_is_var(var_type)) 1517 + continue; 1518 + 1519 + var_name = btf__name_by_offset(btf, var_type->name_off); 1520 + 1521 + for (k = 0; k < npresets; ++k) { 1522 + if (strcmp(var_name, presets[k].name) != 0) 1523 + continue; 1524 + 1525 + if (presets[k].applied) { 1526 + fprintf(stderr, "Variable %s is set more than once", 1527 + var_name); 1528 + return -EINVAL; 1529 + } 1530 + 1531 + err = set_global_var(obj, btf, var_type, map, sinfo, presets + k); 1532 + if (err) 1533 + return err; 1534 + 1535 + presets[k].applied = true; 1536 + break; 1537 + } 1538 + } 1539 + } 1540 + for (i = 0; i < npresets; ++i) { 1541 + if (!presets[i].applied) { 1542 + fprintf(stderr, "Global variable preset %s has not been applied\n", 1543 + presets[i].name); 1544 + } 1545 + presets[i].applied = false; 1546 + } 1547 + return err; 1548 + } 1549 + 1322 1550 static int process_obj(const char *filename) 1323 1551 { 1324 1552 const char *base_filename = basename(strdupa(filename)); ··· 1623 1341 if (prog_cnt == 1) { 1624 1342 prog = bpf_object__next_program(obj, NULL); 1625 1343 bpf_program__set_autoload(prog, true); 1344 + err = set_global_vars(obj, env.presets, env.npresets); 1345 + if (err) { 1346 + fprintf(stderr, "Failed to set global variables %d\n", err); 1347 + goto cleanup; 1348 + } 1626 1349 process_prog(filename, obj, prog); 1627 1350 goto cleanup; 1628 1351 } ··· 1639 1352 if (!tobj) { 1640 1353 err = -errno; 1641 1354 fprintf(stderr, "Failed to open '%s': %d\n", filename, err); 1355 + goto cleanup; 1356 + } 1357 + 1358 + err = set_global_vars(tobj, env.presets, env.npresets); 1359 + if (err) { 1360 + fprintf(stderr, "Failed to set global variables %d\n", err); 1642 1361 goto cleanup; 1643 1362 } 1644 1363 ··· 2753 2460 free(env.deny_filters[i].prog_glob); 2754 2461 } 2755 2462 free(env.deny_filters); 2463 + for (i = 0; i < env.npresets; ++i) { 2464 + free(env.presets[i].name); 2465 + if (env.presets[i].type == ENUMERATOR) 2466 + free(env.presets[i].svalue); 2467 + } 2468 + free(env.presets); 2756 2469 return -err; 2757 2470 }