1 // SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) 2 /* Copyright (c) 2022 Meta Platforms, Inc. and affiliates. */ 3 #define _GNU_SOURCE 4 #include <argp.h> 5 #include <libgen.h> 6 #include <ctype.h> 7 #include <string.h> 8 #include <stdlib.h> 9 #include <sched.h> 10 #include <pthread.h> 11 #include <dirent.h> 12 #include <signal.h> 13 #include <fcntl.h> 14 #include <unistd.h> 15 #include <sys/time.h> 16 #include <sys/sysinfo.h> 17 #include <sys/stat.h> 18 #include <bpf/libbpf.h> 19 #include <bpf/btf.h> 20 #include <bpf/bpf.h> 21 #include <libelf.h> 22 #include <gelf.h> 23 #include <float.h> 24 #include <math.h> 25 #include <limits.h> 26 #include <assert.h> 27 28 #ifndef ARRAY_SIZE 29 #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) 30 #endif 31 32 #ifndef max 33 #define max(a, b) ((a) > (b) ? (a) : (b)) 34 #endif 35 36 #ifndef min 37 #define min(a, b) ((a) < (b) ? (a) : (b)) 38 #endif 39 40 enum stat_id { 41 VERDICT, 42 DURATION, 43 TOTAL_INSNS, 44 TOTAL_STATES, 45 PEAK_STATES, 46 MAX_STATES_PER_INSN, 47 MARK_READ_MAX_LEN, 48 SIZE, 49 JITED_SIZE, 50 STACK, 51 MAX_STACK, 52 PROG_TYPE, 53 ATTACH_TYPE, 54 MEMORY_PEAK, 55 56 FILE_NAME, 57 PROG_NAME, 58 59 ALL_STATS_CNT, 60 NUM_STATS_CNT = FILE_NAME - VERDICT, 61 }; 62 63 /* In comparison mode each stat can specify up to four different values: 64 * - A side value; 65 * - B side value; 66 * - absolute diff value; 67 * - relative (percentage) diff value. 68 * 69 * When specifying stat specs in comparison mode, user can use one of the 70 * following variant suffixes to specify which exact variant should be used for 71 * ordering or filtering: 72 * - `_a` for A side value; 73 * - `_b` for B side value; 74 * - `_diff` for absolute diff value; 75 * - `_pct` for relative (percentage) diff value. 76 * 77 * If no variant suffix is provided, then `_b` (control data) is assumed. 78 * 79 * As an example, let's say instructions stat has the following output: 80 * 81 * Insns (A) Insns (B) Insns (DIFF) 82 * --------- --------- -------------- 83 * 21547 20920 -627 (-2.91%) 84 * 85 * Then: 86 * - 21547 is A side value (insns_a); 87 * - 20920 is B side value (insns_b); 88 * - -627 is absolute diff value (insns_diff); 89 * - -2.91% is relative diff value (insns_pct). 90 * 91 * For verdict there is no verdict_pct variant. 92 * For file and program name, _a and _b variants are equivalent and there are 93 * no _diff or _pct variants. 94 */ 95 enum stat_variant { 96 VARIANT_A, 97 VARIANT_B, 98 VARIANT_DIFF, 99 VARIANT_PCT, 100 }; 101 102 struct verif_stats { 103 char *file_name; 104 char *prog_name; 105 106 long stats[NUM_STATS_CNT]; 107 }; 108 109 /* joined comparison mode stats */ 110 struct verif_stats_join { 111 char *file_name; 112 char *prog_name; 113 114 const struct verif_stats *stats_a; 115 const struct verif_stats *stats_b; 116 }; 117 118 struct stat_specs { 119 int spec_cnt; 120 enum stat_id ids[ALL_STATS_CNT]; 121 enum stat_variant variants[ALL_STATS_CNT]; 122 bool asc[ALL_STATS_CNT]; 123 bool abs[ALL_STATS_CNT]; 124 int lens[ALL_STATS_CNT * 3]; /* 3x for comparison mode */ 125 }; 126 127 enum resfmt { 128 RESFMT_TABLE, 129 RESFMT_TABLE_CALCLEN, /* fake format to pre-calculate table's column widths */ 130 RESFMT_CSV, 131 }; 132 133 enum filter_kind { 134 FILTER_NAME, 135 FILTER_STAT, 136 }; 137 138 enum operator_kind { 139 OP_EQ, /* == or = */ 140 OP_NEQ, /* != or <> */ 141 OP_LT, /* < */ 142 OP_LE, /* <= */ 143 OP_GT, /* > */ 144 OP_GE, /* >= */ 145 }; 146 147 struct filter { 148 enum filter_kind kind; 149 /* FILTER_NAME */ 150 char *any_glob; 151 char *file_glob; 152 char *prog_glob; 153 /* FILTER_STAT */ 154 enum operator_kind op; 155 int stat_id; 156 enum stat_variant stat_var; 157 long value; 158 bool abs; 159 }; 160 161 struct rvalue { 162 enum { INTEGRAL, ENUMERATOR } type; 163 union { 164 long long ivalue; 165 char *svalue; 166 }; 167 }; 168 169 struct field_access { 170 enum { FIELD_NAME, ARRAY_INDEX } type; 171 union { 172 char *name; 173 struct rvalue index; 174 }; 175 }; 176 177 struct var_preset { 178 struct field_access *atoms; 179 int atom_count; 180 char *full_name; 181 struct rvalue value; 182 bool applied; 183 }; 184 185 enum dump_mode { 186 DUMP_NONE = 0, 187 DUMP_XLATED = 1, 188 DUMP_JITED = 2, 189 }; 190 191 static struct env { 192 char **filenames; 193 int filename_cnt; 194 bool verbose; 195 bool debug; 196 bool quiet; 197 bool force_checkpoints; 198 bool force_reg_invariants; 199 enum resfmt out_fmt; 200 bool show_version; 201 bool comparison_mode; 202 bool replay_mode; 203 int top_n; 204 205 int log_level; 206 int log_size; 207 bool log_fixed; 208 209 struct verif_stats *prog_stats; 210 int prog_stat_cnt; 211 212 /* baseline_stats is allocated and used only in comparison mode */ 213 struct verif_stats *baseline_stats; 214 int baseline_stat_cnt; 215 216 struct verif_stats_join *join_stats; 217 int join_stat_cnt; 218 219 struct stat_specs output_spec; 220 struct stat_specs sort_spec; 221 222 struct filter *allow_filters; 223 struct filter *deny_filters; 224 int allow_filter_cnt; 225 int deny_filter_cnt; 226 227 int files_processed; 228 int files_skipped; 229 int progs_processed; 230 int progs_skipped; 231 int top_src_lines; 232 struct var_preset *presets; 233 int npresets; 234 char orig_cgroup[PATH_MAX]; 235 char stat_cgroup[PATH_MAX]; 236 int memory_peak_fd; 237 __u32 dump_mode; 238 } env; 239 240 static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args) 241 { 242 if (!env.verbose) 243 return 0; 244 if (level == LIBBPF_DEBUG && !env.debug) 245 return 0; 246 return vfprintf(stderr, format, args); 247 } 248 249 #define log_errno(fmt, ...) log_errno_aux(__FILE__, __LINE__, fmt, ##__VA_ARGS__) 250 251 __attribute__((format(printf, 3, 4))) 252 static int log_errno_aux(const char *file, int line, const char *fmt, ...) 253 { 254 int err = -errno; 255 va_list ap; 256 257 va_start(ap, fmt); 258 fprintf(stderr, "%s:%d: ", file, line); 259 vfprintf(stderr, fmt, ap); 260 fprintf(stderr, " failed with error '%s'.\n", strerror(errno)); 261 va_end(ap); 262 return err; 263 } 264 265 #ifndef VERISTAT_VERSION 266 #define VERISTAT_VERSION "<kernel>" 267 #endif 268 269 const char *argp_program_version = "veristat v" VERISTAT_VERSION; 270 const char *argp_program_bug_address = "<bpf@vger.kernel.org>"; 271 const char argp_program_doc[] = 272 "veristat BPF verifier stats collection and comparison tool.\n" 273 "\n" 274 "USAGE: veristat <obj-file> [<obj-file>...]\n" 275 " OR: veristat -C <baseline.csv> <comparison.csv>\n" 276 " OR: veristat -R <results.csv>\n" 277 " OR: veristat -vl2 <to_analyze.bpf.o>\n"; 278 279 enum { 280 OPT_LOG_FIXED = 1000, 281 OPT_LOG_SIZE = 1001, 282 OPT_DUMP = 1002, 283 }; 284 285 static const struct argp_option opts[] = { 286 { NULL, 'h', NULL, OPTION_HIDDEN, "Show the full help" }, 287 { "version", 'V', NULL, 0, "Print version" }, 288 { "verbose", 'v', NULL, 0, "Verbose mode" }, 289 { "debug", 'd', NULL, 0, "Debug mode (turns on libbpf debug logging)" }, 290 { "log-level", 'l', "LEVEL", 0, "Verifier log level (default 0 for normal mode, 1 for verbose mode, 2 for full verification log)" }, 291 { "log-fixed", OPT_LOG_FIXED, NULL, 0, "Disable verifier log rotation" }, 292 { "log-size", OPT_LOG_SIZE, "BYTES", 0, "Customize verifier log size (default to 16MB)" }, 293 { "top-n", 'n', "N", 0, "Emit only up to first N results." }, 294 { "quiet", 'q', NULL, 0, "Quiet mode" }, 295 { "emit", 'e', "SPEC", 0, "Specify stats to be emitted" }, 296 { "sort", 's', "SPEC", 0, "Specify sort order" }, 297 { "output-format", 'o', "FMT", 0, "Result output format (table, csv), default is table." }, 298 { "compare", 'C', NULL, 0, "Comparison mode" }, 299 { "replay", 'R', NULL, 0, "Replay mode" }, 300 { "filter", 'f', "FILTER", 0, "Filter expressions (or @filename for file with expressions)." }, 301 { "test-states", 't', NULL, 0, 302 "Force frequent BPF verifier state checkpointing (set BPF_F_TEST_STATE_FREQ program flag)" }, 303 { "test-reg-invariants", 'r', NULL, 0, 304 "Force BPF verifier failure on register invariant violation (BPF_F_TEST_REG_INVARIANTS program flag)" }, 305 { "top-src-lines", 'S', "N", 0, "Emit N most frequent source code lines" }, 306 { "set-global-vars", 'G', "GLOBAL", 0, "Set global variables provided in the expression, for example \"var1 = 1\"" }, 307 { "dump", OPT_DUMP, "DUMP_MODE", OPTION_ARG_OPTIONAL, "Print BPF program dump (xlated, jited)" }, 308 {}, 309 }; 310 311 static int parse_stats(const char *stats_str, struct stat_specs *specs); 312 static int append_filter(struct filter **filters, int *cnt, const char *str); 313 static int append_filter_file(const char *path); 314 static int append_var_preset(struct var_preset **presets, int *cnt, const char *expr); 315 static int append_var_preset_file(const char *filename); 316 static int append_file(const char *path); 317 static int append_file_from_file(const char *path); 318 319 static error_t parse_arg(int key, char *arg, struct argp_state *state) 320 { 321 int err; 322 323 switch (key) { 324 case 'h': 325 argp_state_help(state, stderr, ARGP_HELP_STD_HELP); 326 break; 327 case 'V': 328 env.show_version = true; 329 break; 330 case 'v': 331 env.verbose = true; 332 break; 333 case 'd': 334 env.debug = true; 335 env.verbose = true; 336 break; 337 case 'q': 338 env.quiet = true; 339 break; 340 case 'e': 341 err = parse_stats(arg, &env.output_spec); 342 if (err) 343 return err; 344 break; 345 case 's': 346 err = parse_stats(arg, &env.sort_spec); 347 if (err) 348 return err; 349 break; 350 case 'o': 351 if (strcmp(arg, "table") == 0) { 352 env.out_fmt = RESFMT_TABLE; 353 } else if (strcmp(arg, "csv") == 0) { 354 env.out_fmt = RESFMT_CSV; 355 } else { 356 fprintf(stderr, "Unrecognized output format '%s'\n", arg); 357 return -EINVAL; 358 } 359 break; 360 case 'l': 361 errno = 0; 362 env.log_level = strtol(arg, NULL, 10); 363 if (errno) { 364 fprintf(stderr, "invalid log level: %s\n", arg); 365 argp_usage(state); 366 } 367 break; 368 case OPT_LOG_FIXED: 369 env.log_fixed = true; 370 break; 371 case OPT_LOG_SIZE: 372 errno = 0; 373 env.log_size = strtol(arg, NULL, 10); 374 if (errno) { 375 fprintf(stderr, "invalid log size: %s\n", arg); 376 argp_usage(state); 377 } 378 break; 379 case 't': 380 env.force_checkpoints = true; 381 break; 382 case 'r': 383 env.force_reg_invariants = true; 384 break; 385 case 'n': 386 errno = 0; 387 env.top_n = strtol(arg, NULL, 10); 388 if (errno) { 389 fprintf(stderr, "invalid top N specifier: %s\n", arg); 390 argp_usage(state); 391 } 392 break; 393 case 'C': 394 env.comparison_mode = true; 395 break; 396 case 'R': 397 env.replay_mode = true; 398 break; 399 case 'f': 400 if (arg[0] == '@') 401 err = append_filter_file(arg + 1); 402 else if (arg[0] == '!') 403 err = append_filter(&env.deny_filters, &env.deny_filter_cnt, arg + 1); 404 else 405 err = append_filter(&env.allow_filters, &env.allow_filter_cnt, arg); 406 if (err) { 407 fprintf(stderr, "Failed to collect program filter expressions: %d\n", err); 408 return err; 409 } 410 break; 411 case 'S': 412 errno = 0; 413 env.top_src_lines = strtol(arg, NULL, 10); 414 if (errno) { 415 fprintf(stderr, "invalid top lines N specifier: %s\n", arg); 416 argp_usage(state); 417 } 418 break; 419 case 'G': { 420 if (arg[0] == '@') 421 err = append_var_preset_file(arg + 1); 422 else 423 err = append_var_preset(&env.presets, &env.npresets, arg); 424 if (err) { 425 fprintf(stderr, "Failed to parse global variable presets: %s\n", arg); 426 return err; 427 } 428 break; 429 } 430 case ARGP_KEY_ARG: 431 if (arg[0] == '@') 432 err = append_file_from_file(arg + 1); 433 else 434 err = append_file(arg); 435 if (err) { 436 fprintf(stderr, "Failed to collect BPF object files: %d\n", err); 437 return err; 438 } 439 break; 440 case OPT_DUMP: 441 if (!arg || strcasecmp(arg, "xlated") == 0) { 442 env.dump_mode |= DUMP_XLATED; 443 } else if (strcasecmp(arg, "jited") == 0) { 444 env.dump_mode |= DUMP_JITED; 445 } else { 446 fprintf(stderr, "Unrecognized dump mode '%s'\n", arg); 447 return -EINVAL; 448 } 449 break; 450 default: 451 return ARGP_ERR_UNKNOWN; 452 } 453 return 0; 454 } 455 456 static const struct argp argp = { 457 .options = opts, 458 .parser = parse_arg, 459 .doc = argp_program_doc, 460 }; 461 462 463 /* Adapted from perf/util/string.c */ 464 static bool glob_matches(const char *str, const char *pat) 465 { 466 while (*str && *pat && *pat != '*') { 467 if (*str != *pat) 468 return false; 469 str++; 470 pat++; 471 } 472 /* Check wild card */ 473 if (*pat == '*') { 474 while (*pat == '*') 475 pat++; 476 if (!*pat) /* Tail wild card matches all */ 477 return true; 478 while (*str) 479 if (glob_matches(str++, pat)) 480 return true; 481 } 482 return !*str && !*pat; 483 } 484 485 static bool is_bpf_obj_file(const char *path) { 486 Elf64_Ehdr *ehdr; 487 int fd, err = -EINVAL; 488 Elf *elf = NULL; 489 490 fd = open(path, O_RDONLY | O_CLOEXEC); 491 if (fd < 0) 492 return true; /* we'll fail later and propagate error */ 493 494 /* ensure libelf is initialized */ 495 (void)elf_version(EV_CURRENT); 496 497 elf = elf_begin(fd, ELF_C_READ, NULL); 498 if (!elf) 499 goto cleanup; 500 501 if (elf_kind(elf) != ELF_K_ELF || gelf_getclass(elf) != ELFCLASS64) 502 goto cleanup; 503 504 ehdr = elf64_getehdr(elf); 505 /* Old LLVM set e_machine to EM_NONE */ 506 if (!ehdr || ehdr->e_type != ET_REL || (ehdr->e_machine && ehdr->e_machine != EM_BPF)) 507 goto cleanup; 508 509 err = 0; 510 cleanup: 511 if (elf) 512 elf_end(elf); 513 close(fd); 514 return err == 0; 515 } 516 517 /* Exact filter match */ 518 static bool name_filter_matches(struct filter *f, const char *filename, const char *prog_name) 519 { 520 if (f->any_glob) 521 return glob_matches(filename, f->any_glob) || 522 (prog_name && glob_matches(prog_name, f->any_glob)); 523 if (f->file_glob && f->prog_glob) 524 return prog_name && 525 glob_matches(filename, f->file_glob) && 526 glob_matches(prog_name, f->prog_glob); 527 if (f->file_glob) 528 return glob_matches(filename, f->file_glob); 529 if (f->prog_glob) 530 return prog_name && glob_matches(prog_name, f->prog_glob); 531 return false; 532 } 533 534 /* Check if the filter does not outright reject the file name */ 535 static bool name_filter_may_match(struct filter *f, const char *filename) 536 { 537 if (f->file_glob) 538 return glob_matches(filename, f->file_glob); 539 /* 540 * If we don't know program name yet, any_glob filter 541 * has to assume that current BPF object file might be 542 * relevant; we'll check again later on after opening 543 * BPF object file, at which point program name will 544 * be known finally. 545 */ 546 if (f->any_glob || f->prog_glob) 547 return true; 548 return false; 549 } 550 551 static bool should_process_file_prog(const char *filename, const char *prog_name) 552 { 553 struct filter *f; 554 int i, allow_cnt = 0; 555 556 for (i = 0; i < env.deny_filter_cnt; i++) { 557 f = &env.deny_filters[i]; 558 if (f->kind == FILTER_NAME && name_filter_matches(f, filename, prog_name)) 559 return false; 560 } 561 562 for (i = 0; i < env.allow_filter_cnt; i++) { 563 f = &env.allow_filters[i]; 564 if (f->kind != FILTER_NAME) 565 continue; 566 567 allow_cnt++; 568 if (prog_name && name_filter_matches(f, filename, prog_name)) 569 return true; 570 /* 571 * If there is no prog_name and the file name is not blocked by 572 * the filter, allow to open the file. Afterwards there would be 573 * a second refining query with prog_name set. 574 */ 575 if (!prog_name && name_filter_may_match(f, filename)) 576 return true; 577 } 578 579 /* if there are no file/prog name allow filters, allow all progs, 580 * unless they are denied earlier explicitly 581 */ 582 return allow_cnt == 0; 583 } 584 585 static struct { 586 enum operator_kind op_kind; 587 const char *op_str; 588 } operators[] = { 589 /* Order of these definitions matter to avoid situations like '<' 590 * matching part of what is actually a '<>' operator. That is, 591 * substrings should go last. 592 */ 593 { OP_EQ, "==" }, 594 { OP_NEQ, "!=" }, 595 { OP_NEQ, "<>" }, 596 { OP_LE, "<=" }, 597 { OP_LT, "<" }, 598 { OP_GE, ">=" }, 599 { OP_GT, ">" }, 600 { OP_EQ, "=" }, 601 }; 602 603 static bool parse_stat_id_var(const char *name, size_t len, int *id, 604 enum stat_variant *var, bool *is_abs); 605 606 static int append_filter(struct filter **filters, int *cnt, const char *str) 607 { 608 struct filter *f; 609 void *tmp; 610 const char *p; 611 int i; 612 613 tmp = realloc(*filters, (*cnt + 1) * sizeof(**filters)); 614 if (!tmp) 615 return -ENOMEM; 616 *filters = tmp; 617 618 f = &(*filters)[*cnt]; 619 memset(f, 0, sizeof(*f)); 620 621 /* First, let's check if it's a stats filter of the following form: 622 * <stat><op><value, where: 623 * - <stat> is one of supported numerical stats (verdict is also 624 * considered numerical, failure == 0, success == 1); 625 * - <op> is comparison operator (see `operators` definitions); 626 * - <value> is an integer (or failure/success, or false/true as 627 * special aliases for 0 and 1, respectively). 628 * If the form doesn't match what user provided, we assume file/prog 629 * glob filter. 630 */ 631 for (i = 0; i < ARRAY_SIZE(operators); i++) { 632 enum stat_variant var; 633 int id; 634 long val; 635 const char *end = str; 636 const char *op_str; 637 bool is_abs; 638 639 op_str = operators[i].op_str; 640 p = strstr(str, op_str); 641 if (!p) 642 continue; 643 644 if (!parse_stat_id_var(str, p - str, &id, &var, &is_abs)) { 645 fprintf(stderr, "Unrecognized stat name in '%s'!\n", str); 646 return -EINVAL; 647 } 648 if (id >= FILE_NAME) { 649 fprintf(stderr, "Non-integer stat is specified in '%s'!\n", str); 650 return -EINVAL; 651 } 652 653 p += strlen(op_str); 654 655 if (strcasecmp(p, "true") == 0 || 656 strcasecmp(p, "t") == 0 || 657 strcasecmp(p, "success") == 0 || 658 strcasecmp(p, "succ") == 0 || 659 strcasecmp(p, "s") == 0 || 660 strcasecmp(p, "match") == 0 || 661 strcasecmp(p, "m") == 0) { 662 val = 1; 663 } else if (strcasecmp(p, "false") == 0 || 664 strcasecmp(p, "f") == 0 || 665 strcasecmp(p, "failure") == 0 || 666 strcasecmp(p, "fail") == 0 || 667 strcasecmp(p, "mismatch") == 0 || 668 strcasecmp(p, "mis") == 0) { 669 val = 0; 670 } else { 671 errno = 0; 672 val = strtol(p, (char **)&end, 10); 673 if (errno || end == p || *end != '\0' ) { 674 fprintf(stderr, "Invalid integer value in '%s'!\n", str); 675 return -EINVAL; 676 } 677 } 678 679 f->kind = FILTER_STAT; 680 f->stat_id = id; 681 f->stat_var = var; 682 f->op = operators[i].op_kind; 683 f->abs = true; 684 f->value = val; 685 686 *cnt += 1; 687 return 0; 688 } 689 690 /* File/prog filter can be specified either as '<glob>' or 691 * '<file-glob>/<prog-glob>'. In the former case <glob> is applied to 692 * both file and program names. This seems to be way more useful in 693 * practice. If user needs full control, they can use '/<prog-glob>' 694 * form to glob just program name, or '<file-glob>/' to glob only file 695 * name. But usually common <glob> seems to be the most useful and 696 * ergonomic way. 697 */ 698 f->kind = FILTER_NAME; 699 p = strchr(str, '/'); 700 if (!p) { 701 f->any_glob = strdup(str); 702 if (!f->any_glob) 703 return -ENOMEM; 704 } else { 705 if (str != p) { 706 /* non-empty file glob */ 707 f->file_glob = strndup(str, p - str); 708 if (!f->file_glob) 709 return -ENOMEM; 710 } 711 if (strlen(p + 1) > 0) { 712 /* non-empty prog glob */ 713 f->prog_glob = strdup(p + 1); 714 if (!f->prog_glob) { 715 free(f->file_glob); 716 f->file_glob = NULL; 717 return -ENOMEM; 718 } 719 } 720 } 721 722 if ((!f->any_glob && !f->file_glob && !f->prog_glob) || 723 (f->any_glob && strcmp(f->any_glob, "") == 0)) { 724 fprintf(stderr, "Invalid filter: '%s'\n", str); 725 return -EINVAL; 726 } 727 728 *cnt += 1; 729 return 0; 730 } 731 732 static int append_filter_file(const char *path) 733 { 734 char buf[1024]; 735 FILE *f; 736 int err = 0; 737 738 f = fopen(path, "r"); 739 if (!f) { 740 err = -errno; 741 fprintf(stderr, "Failed to open filters in '%s': %s\n", path, strerror(-err)); 742 return err; 743 } 744 745 while (fscanf(f, " %1023[^\n]\n", buf) == 1) { 746 /* lines starting with # are comments, skip them */ 747 if (buf[0] == '\0' || buf[0] == '#') 748 continue; 749 /* lines starting with ! are negative match filters */ 750 if (buf[0] == '!') 751 err = append_filter(&env.deny_filters, &env.deny_filter_cnt, buf + 1); 752 else 753 err = append_filter(&env.allow_filters, &env.allow_filter_cnt, buf); 754 if (err) 755 goto cleanup; 756 } 757 758 cleanup: 759 fclose(f); 760 return err; 761 } 762 763 static const struct stat_specs default_output_spec = { 764 .spec_cnt = 8, 765 .ids = { 766 FILE_NAME, PROG_NAME, VERDICT, DURATION, 767 TOTAL_INSNS, TOTAL_STATES, SIZE, JITED_SIZE 768 }, 769 }; 770 771 static int append_file(const char *path) 772 { 773 void *tmp; 774 775 tmp = realloc(env.filenames, (env.filename_cnt + 1) * sizeof(*env.filenames)); 776 if (!tmp) 777 return -ENOMEM; 778 env.filenames = tmp; 779 env.filenames[env.filename_cnt] = strdup(path); 780 if (!env.filenames[env.filename_cnt]) 781 return -ENOMEM; 782 env.filename_cnt++; 783 return 0; 784 } 785 786 static int append_file_from_file(const char *path) 787 { 788 char buf[1024]; 789 int err = 0; 790 FILE *f; 791 792 f = fopen(path, "r"); 793 if (!f) { 794 err = -errno; 795 fprintf(stderr, "Failed to open object files list in '%s': %s\n", 796 path, strerror(errno)); 797 return err; 798 } 799 800 while (fscanf(f, " %1023[^\n]\n", buf) == 1) { 801 /* lines starting with # are comments, skip them */ 802 if (buf[0] == '\0' || buf[0] == '#') 803 continue; 804 err = append_file(buf); 805 if (err) 806 goto cleanup; 807 } 808 809 cleanup: 810 fclose(f); 811 return err; 812 } 813 814 static const struct stat_specs default_csv_output_spec = { 815 .spec_cnt = 16, 816 .ids = { 817 FILE_NAME, PROG_NAME, VERDICT, DURATION, 818 TOTAL_INSNS, TOTAL_STATES, PEAK_STATES, 819 MAX_STATES_PER_INSN, MARK_READ_MAX_LEN, 820 SIZE, JITED_SIZE, PROG_TYPE, ATTACH_TYPE, 821 STACK, MAX_STACK, MEMORY_PEAK, 822 }, 823 }; 824 825 static const struct stat_specs default_sort_spec = { 826 .spec_cnt = 2, 827 .ids = { 828 FILE_NAME, PROG_NAME, 829 }, 830 .asc = { true, true, }, 831 }; 832 833 /* sorting for comparison mode to join two data sets */ 834 static const struct stat_specs join_sort_spec = { 835 .spec_cnt = 2, 836 .ids = { 837 FILE_NAME, PROG_NAME, 838 }, 839 .asc = { true, true, }, 840 }; 841 842 static struct stat_def { 843 const char *header; 844 const char *names[4]; 845 bool asc_by_default; 846 bool left_aligned; 847 } stat_defs[] = { 848 [FILE_NAME] = { "File", {"file_name", "filename", "file"}, true /* asc */, true /* left */ }, 849 [PROG_NAME] = { "Program", {"prog_name", "progname", "prog"}, true /* asc */, true /* left */ }, 850 [VERDICT] = { "Verdict", {"verdict"}, true /* asc: failure, success */, true /* left */ }, 851 [DURATION] = { "Duration (us)", {"duration", "dur"}, }, 852 [TOTAL_INSNS] = { "Insns", {"total_insns", "insns"}, }, 853 [TOTAL_STATES] = { "States", {"total_states", "states"}, }, 854 [PEAK_STATES] = { "Peak states", {"peak_states"}, }, 855 [MAX_STATES_PER_INSN] = { "Max states per insn", {"max_states_per_insn"}, }, 856 [MARK_READ_MAX_LEN] = { "Max mark read length", {"max_mark_read_len", "mark_read"}, }, 857 [SIZE] = { "Program size", {"prog_size"}, }, 858 [JITED_SIZE] = { "Jited size", {"prog_size_jited"}, }, 859 [STACK] = {"Stack depth", {"stack_depth", "stack"}, }, 860 [MAX_STACK] = {"Max stack depth", {"max_stack_depth"}, }, 861 [PROG_TYPE] = { "Program type", {"prog_type"}, }, 862 [ATTACH_TYPE] = { "Attach type", {"attach_type", }, }, 863 [MEMORY_PEAK] = { "Peak memory (MiB)", {"mem_peak", }, }, 864 }; 865 866 static bool parse_stat_id_var(const char *name, size_t len, int *id, 867 enum stat_variant *var, bool *is_abs) 868 { 869 static const char *var_sfxs[] = { 870 [VARIANT_A] = "_a", 871 [VARIANT_B] = "_b", 872 [VARIANT_DIFF] = "_diff", 873 [VARIANT_PCT] = "_pct", 874 }; 875 int i, j, k; 876 877 /* |<stat>| means we take absolute value of given stat */ 878 *is_abs = false; 879 if (len > 2 && name[0] == '|' && name[len - 1] == '|') { 880 *is_abs = true; 881 name += 1; 882 len -= 2; 883 } 884 885 for (i = 0; i < ARRAY_SIZE(stat_defs); i++) { 886 struct stat_def *def = &stat_defs[i]; 887 size_t alias_len, sfx_len; 888 const char *alias; 889 890 for (j = 0; j < ARRAY_SIZE(stat_defs[i].names); j++) { 891 alias = def->names[j]; 892 if (!alias) 893 continue; 894 895 alias_len = strlen(alias); 896 if (strncmp(name, alias, alias_len) != 0) 897 continue; 898 899 if (alias_len == len) { 900 /* If no variant suffix is specified, we 901 * assume control group (just in case we are 902 * in comparison mode. Variant is ignored in 903 * non-comparison mode. 904 */ 905 *var = VARIANT_B; 906 *id = i; 907 return true; 908 } 909 910 for (k = 0; k < ARRAY_SIZE(var_sfxs); k++) { 911 sfx_len = strlen(var_sfxs[k]); 912 if (alias_len + sfx_len != len) 913 continue; 914 915 if (strncmp(name + alias_len, var_sfxs[k], sfx_len) == 0) { 916 *var = (enum stat_variant)k; 917 *id = i; 918 return true; 919 } 920 } 921 } 922 } 923 924 return false; 925 } 926 927 static bool is_asc_sym(char c) 928 { 929 return c == '^'; 930 } 931 932 static bool is_desc_sym(char c) 933 { 934 return c == 'v' || c == 'V' || c == '.' || c == '!' || c == '_'; 935 } 936 937 static char *rtrim(char *str) 938 { 939 int i; 940 941 for (i = strlen(str) - 1; i > 0; --i) { 942 if (!isspace(str[i])) 943 break; 944 str[i] = '\0'; 945 } 946 return str; 947 } 948 949 static int parse_stat(const char *stat_name, struct stat_specs *specs) 950 { 951 int id; 952 bool has_order = false, is_asc = false, is_abs = false; 953 size_t len = strlen(stat_name); 954 enum stat_variant var; 955 956 if (specs->spec_cnt >= ARRAY_SIZE(specs->ids)) { 957 fprintf(stderr, "Can't specify more than %zd stats\n", ARRAY_SIZE(specs->ids)); 958 return -E2BIG; 959 } 960 961 if (len > 1 && (is_asc_sym(stat_name[len - 1]) || is_desc_sym(stat_name[len - 1]))) { 962 has_order = true; 963 is_asc = is_asc_sym(stat_name[len - 1]); 964 len -= 1; 965 } 966 967 if (!parse_stat_id_var(stat_name, len, &id, &var, &is_abs)) { 968 fprintf(stderr, "Unrecognized stat name '%s'\n", stat_name); 969 return -ESRCH; 970 } 971 972 specs->ids[specs->spec_cnt] = id; 973 specs->variants[specs->spec_cnt] = var; 974 specs->asc[specs->spec_cnt] = has_order ? is_asc : stat_defs[id].asc_by_default; 975 specs->abs[specs->spec_cnt] = is_abs; 976 specs->spec_cnt++; 977 978 return 0; 979 } 980 981 static int parse_stats(const char *stats_str, struct stat_specs *specs) 982 { 983 char *input, *state = NULL, *next; 984 int err, cnt = 0; 985 986 input = strdup(stats_str); 987 if (!input) 988 return -ENOMEM; 989 990 while ((next = strtok_r(cnt++ ? NULL : input, ",", &state))) { 991 err = parse_stat(next, specs); 992 if (err) { 993 free(input); 994 return err; 995 } 996 } 997 998 free(input); 999 return 0; 1000 } 1001 1002 static void free_verif_stats(struct verif_stats *stats, size_t stat_cnt) 1003 { 1004 int i; 1005 1006 if (!stats) 1007 return; 1008 1009 for (i = 0; i < stat_cnt; i++) { 1010 free(stats[i].file_name); 1011 free(stats[i].prog_name); 1012 } 1013 free(stats); 1014 } 1015 1016 static char verif_log_buf[64 * 1024]; 1017 1018 /* Keep room for all 256 subprogram records and trailing statistics. */ 1019 #define MAX_PARSED_LOG_LINES 300 1020 1021 static int parse_verif_log(char * const buf, size_t buf_sz, struct verif_stats *s) 1022 { 1023 const char *cur; 1024 long sub_stack; 1025 int pos, lines, cnt = 0; 1026 char *state = NULL, *token, stack[512] = {}; 1027 1028 buf[buf_sz - 1] = '\0'; 1029 1030 for (pos = strlen(buf) - 1, lines = 0; pos >= 0 && lines < MAX_PARSED_LOG_LINES; lines++) { 1031 /* find previous endline or otherwise take the start of log buf */ 1032 for (cur = &buf[pos]; cur > buf && cur[0] != '\n'; cur--, pos--) { 1033 } 1034 /* next time start from end of previous line (or pos goes to <0) */ 1035 pos--; 1036 /* if we found endline, point right after endline symbol; 1037 * otherwise, stay at the beginning of log buf 1038 */ 1039 if (cur[0] == '\n') 1040 cur++; 1041 1042 if (1 == sscanf(cur, "verification time %ld usec\n", &s->stats[DURATION])) 1043 continue; 1044 if (5 == sscanf(cur, "processed %ld insns (limit %*d) max_states_per_insn %ld total_states %ld peak_states %ld mark_read %ld", 1045 &s->stats[TOTAL_INSNS], 1046 &s->stats[MAX_STATES_PER_INSN], 1047 &s->stats[TOTAL_STATES], 1048 &s->stats[PEAK_STATES], 1049 &s->stats[MARK_READ_MAX_LEN])) 1050 continue; 1051 1052 /* 1053 * New kernels emit one "subprog <id> (<name>) <kind>" record 1054 * per subprogram with the stack depth at the end, while old 1055 * kernels emit a single "stack depth <a+...+n> max <max>" 1056 * line. Match both formats so veristat works against either 1057 * kernel. 1058 */ 1059 if (sscanf(cur, "stack depth max %ld", &s->stats[MAX_STACK]) == 1) 1060 continue; 1061 if (sscanf(cur, "subprog %*d %*s %*s insns_self %*d insns_total %*d stack %ld", &sub_stack) == 1) { 1062 s->stats[STACK] += sub_stack; 1063 continue; 1064 } 1065 if (2 == sscanf(cur, "stack depth %511s max %ld", stack, &s->stats[MAX_STACK])) 1066 continue; 1067 } 1068 while ((token = strtok_r(cnt++ ? NULL : stack, "+", &state))) { 1069 if (sscanf(token, "%ld", &sub_stack) == 0) 1070 break; 1071 s->stats[STACK] += sub_stack; 1072 } 1073 return 0; 1074 } 1075 1076 struct line_cnt { 1077 char *line; 1078 int cnt; 1079 }; 1080 1081 static int str_cmp(const void *a, const void *b) 1082 { 1083 const char **str1 = (const char **)a; 1084 const char **str2 = (const char **)b; 1085 1086 return strcmp(*str1, *str2); 1087 } 1088 1089 static int line_cnt_cmp(const void *a, const void *b) 1090 { 1091 const struct line_cnt *a_cnt = (const struct line_cnt *)a; 1092 const struct line_cnt *b_cnt = (const struct line_cnt *)b; 1093 1094 if (a_cnt->cnt != b_cnt->cnt) 1095 return a_cnt->cnt > b_cnt->cnt ? -1 : 1; 1096 return strcmp(a_cnt->line, b_cnt->line); 1097 } 1098 1099 static int print_top_src_lines(char * const buf, size_t buf_sz, const char *prog_name) 1100 { 1101 int lines_cap = 0; 1102 int lines_size = 0; 1103 char **lines = NULL; 1104 char *line = NULL; 1105 char *state; 1106 struct line_cnt *freq = NULL; 1107 struct line_cnt *cur; 1108 int unique_lines; 1109 int err = 0; 1110 int i; 1111 1112 while ((line = strtok_r(line ? NULL : buf, "\n", &state))) { 1113 if (strncmp(line, "; ", 2) != 0) 1114 continue; 1115 line += 2; 1116 1117 if (lines_size == lines_cap) { 1118 char **tmp; 1119 1120 lines_cap = max(16, lines_cap * 2); 1121 tmp = realloc(lines, lines_cap * sizeof(*tmp)); 1122 if (!tmp) { 1123 err = -ENOMEM; 1124 goto cleanup; 1125 } 1126 lines = tmp; 1127 } 1128 lines[lines_size] = line; 1129 lines_size++; 1130 } 1131 1132 if (lines_size == 0) 1133 goto cleanup; 1134 1135 qsort(lines, lines_size, sizeof(*lines), str_cmp); 1136 1137 freq = calloc(lines_size, sizeof(*freq)); 1138 if (!freq) { 1139 err = -ENOMEM; 1140 goto cleanup; 1141 } 1142 1143 cur = freq; 1144 cur->line = lines[0]; 1145 cur->cnt = 1; 1146 for (i = 1; i < lines_size; ++i) { 1147 if (strcmp(lines[i], cur->line) != 0) { 1148 cur++; 1149 cur->line = lines[i]; 1150 cur->cnt = 0; 1151 } 1152 cur->cnt++; 1153 } 1154 unique_lines = cur - freq + 1; 1155 1156 qsort(freq, unique_lines, sizeof(struct line_cnt), line_cnt_cmp); 1157 1158 printf("Top source lines (%s):\n", prog_name); 1159 for (i = 0; i < min(unique_lines, env.top_src_lines); ++i) { 1160 const char *src_code = freq[i].line; 1161 const char *src_line = NULL; 1162 char *split = strrchr(freq[i].line, '@'); 1163 1164 if (split) { 1165 src_line = split + 1; 1166 1167 while (*src_line && isspace(*src_line)) 1168 src_line++; 1169 1170 while (split > src_code && isspace(*split)) 1171 split--; 1172 *split = '\0'; 1173 } 1174 1175 if (src_line) 1176 printf("%5d: (%s)\t%s\n", freq[i].cnt, src_line, src_code); 1177 else 1178 printf("%5d: %s\n", freq[i].cnt, src_code); 1179 } 1180 printf("\n"); 1181 1182 cleanup: 1183 free(freq); 1184 free(lines); 1185 return err; 1186 } 1187 1188 static int guess_prog_type_by_ctx_name(const char *ctx_name, 1189 enum bpf_prog_type *prog_type, 1190 enum bpf_attach_type *attach_type) 1191 { 1192 /* We need to guess program type based on its declared context type. 1193 * This guess can't be perfect as many different program types might 1194 * share the same context type. So we can only hope to reasonably 1195 * well guess this and get lucky. 1196 * 1197 * Just in case, we support both UAPI-side type names and 1198 * kernel-internal names. 1199 */ 1200 static struct { 1201 const char *uapi_name; 1202 const char *kern_name; 1203 enum bpf_prog_type prog_type; 1204 enum bpf_attach_type attach_type; 1205 } ctx_map[] = { 1206 /* __sk_buff is most ambiguous, we assume TC program */ 1207 { "__sk_buff", "sk_buff", BPF_PROG_TYPE_SCHED_CLS }, 1208 { "bpf_sock", "sock", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET4_POST_BIND }, 1209 { "bpf_sock_addr", "bpf_sock_addr_kern", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET4_BIND }, 1210 { "bpf_sock_ops", "bpf_sock_ops_kern", BPF_PROG_TYPE_SOCK_OPS, BPF_CGROUP_SOCK_OPS }, 1211 { "sk_msg_md", "sk_msg", BPF_PROG_TYPE_SK_MSG, BPF_SK_MSG_VERDICT }, 1212 { "bpf_cgroup_dev_ctx", "bpf_cgroup_dev_ctx", BPF_PROG_TYPE_CGROUP_DEVICE, BPF_CGROUP_DEVICE }, 1213 { "bpf_sysctl", "bpf_sysctl_kern", BPF_PROG_TYPE_CGROUP_SYSCTL, BPF_CGROUP_SYSCTL }, 1214 { "bpf_sockopt", "bpf_sockopt_kern", BPF_PROG_TYPE_CGROUP_SOCKOPT, BPF_CGROUP_SETSOCKOPT }, 1215 { "sk_reuseport_md", "sk_reuseport_kern", BPF_PROG_TYPE_SK_REUSEPORT, BPF_SK_REUSEPORT_SELECT_OR_MIGRATE }, 1216 { "bpf_sk_lookup", "bpf_sk_lookup_kern", BPF_PROG_TYPE_SK_LOOKUP, BPF_SK_LOOKUP }, 1217 { "xdp_md", "xdp_buff", BPF_PROG_TYPE_XDP, BPF_XDP }, 1218 /* tracing types with no expected attach type */ 1219 { "bpf_user_pt_regs_t", "pt_regs", BPF_PROG_TYPE_KPROBE }, 1220 { "bpf_perf_event_data", "bpf_perf_event_data_kern", BPF_PROG_TYPE_PERF_EVENT }, 1221 /* raw_tp programs use u64[] from kernel side, we don't want 1222 * to match on that, probably; so NULL for kern-side type 1223 */ 1224 { "bpf_raw_tracepoint_args", NULL, BPF_PROG_TYPE_RAW_TRACEPOINT }, 1225 }; 1226 int i; 1227 1228 if (!ctx_name) 1229 return -EINVAL; 1230 1231 for (i = 0; i < ARRAY_SIZE(ctx_map); i++) { 1232 if (strcmp(ctx_map[i].uapi_name, ctx_name) == 0 || 1233 (ctx_map[i].kern_name && strcmp(ctx_map[i].kern_name, ctx_name) == 0)) { 1234 *prog_type = ctx_map[i].prog_type; 1235 *attach_type = ctx_map[i].attach_type; 1236 return 0; 1237 } 1238 } 1239 1240 return -ESRCH; 1241 } 1242 1243 /* Make sure only target program is referenced from struct_ops map, 1244 * otherwise libbpf would automatically set autocreate for all 1245 * referenced programs. 1246 * See libbpf.c:bpf_object_adjust_struct_ops_autoload. 1247 */ 1248 static void mask_unrelated_struct_ops_progs(struct bpf_object *obj, 1249 struct bpf_map *map, 1250 struct bpf_program *prog) 1251 { 1252 struct btf *btf = bpf_object__btf(obj); 1253 const struct btf_type *t, *mt; 1254 struct btf_member *m; 1255 int i, moff; 1256 size_t data_sz, ptr_sz = sizeof(void *); 1257 void *data; 1258 1259 t = btf__type_by_id(btf, bpf_map__btf_value_type_id(map)); 1260 if (!btf_is_struct(t)) 1261 return; 1262 1263 data = bpf_map__initial_value(map, &data_sz); 1264 for (i = 0; i < btf_vlen(t); i++) { 1265 m = &btf_members(t)[i]; 1266 mt = btf__type_by_id(btf, m->type); 1267 if (!btf_is_ptr(mt)) 1268 continue; 1269 moff = m->offset / 8; 1270 if (moff + ptr_sz > data_sz) 1271 continue; 1272 if (memcmp(data + moff, &prog, ptr_sz) == 0) 1273 continue; 1274 memset(data + moff, 0, ptr_sz); 1275 } 1276 } 1277 1278 static void fixup_obj_maps(struct bpf_object *obj) 1279 { 1280 struct bpf_map *map; 1281 1282 bpf_object__for_each_map(map, obj) { 1283 /* disable pinning */ 1284 bpf_map__set_pin_path(map, NULL); 1285 1286 /* fix up map size, if necessary */ 1287 switch (bpf_map__type(map)) { 1288 /* 1289 * if the verifier doesn't use max_entries 1290 * then set to 1 to avoid -ENOMEM 1291 */ 1292 case BPF_MAP_TYPE_HASH: 1293 case BPF_MAP_TYPE_PERCPU_HASH: 1294 case BPF_MAP_TYPE_LRU_HASH: 1295 case BPF_MAP_TYPE_LRU_PERCPU_HASH: 1296 case BPF_MAP_TYPE_SOCKHASH: 1297 case BPF_MAP_TYPE_DEVMAP_HASH: 1298 case BPF_MAP_TYPE_QUEUE: 1299 case BPF_MAP_TYPE_STACK: 1300 case BPF_MAP_TYPE_BLOOM_FILTER: 1301 case BPF_MAP_TYPE_STACK_TRACE: 1302 bpf_map__set_max_entries(map, 1); 1303 break; 1304 1305 /* ringbufs must be page-aligned */ 1306 case BPF_MAP_TYPE_RINGBUF: 1307 case BPF_MAP_TYPE_USER_RINGBUF: 1308 bpf_map__set_max_entries(map, sysconf(_SC_PAGESIZE)); 1309 break; 1310 1311 case BPF_MAP_TYPE_SK_STORAGE: 1312 case BPF_MAP_TYPE_TASK_STORAGE: 1313 case BPF_MAP_TYPE_INODE_STORAGE: 1314 case BPF_MAP_TYPE_CGROUP_STORAGE: 1315 case BPF_MAP_TYPE_CGRP_STORAGE: 1316 case BPF_MAP_TYPE_STRUCT_OPS: 1317 break; 1318 default: 1319 if (bpf_map__max_entries(map) == 0) 1320 bpf_map__set_max_entries(map, 1); 1321 } 1322 } 1323 } 1324 1325 static void fixup_obj(struct bpf_object *obj, struct bpf_program *prog, const char *filename) 1326 { 1327 struct bpf_map *map; 1328 1329 bpf_object__for_each_map(map, obj) { 1330 if (bpf_map__type(map) == BPF_MAP_TYPE_STRUCT_OPS) 1331 mask_unrelated_struct_ops_progs(obj, map, prog); 1332 } 1333 1334 /* SEC(freplace) programs can't be loaded with veristat as is, 1335 * but we can try guessing their target program's expected type by 1336 * looking at the type of program's first argument and substituting 1337 * corresponding program type 1338 */ 1339 if (bpf_program__type(prog) == BPF_PROG_TYPE_EXT) { 1340 const struct btf *btf = bpf_object__btf(obj); 1341 const char *prog_name = bpf_program__name(prog); 1342 enum bpf_prog_type prog_type; 1343 enum bpf_attach_type attach_type; 1344 const struct btf_type *t; 1345 const char *ctx_name; 1346 int id; 1347 1348 if (!btf) 1349 goto skip_freplace_fixup; 1350 1351 id = btf__find_by_name_kind(btf, prog_name, BTF_KIND_FUNC); 1352 t = btf__type_by_id(btf, id); 1353 t = btf__type_by_id(btf, t->type); 1354 if (!btf_is_func_proto(t) || btf_vlen(t) != 1) 1355 goto skip_freplace_fixup; 1356 1357 /* context argument is a pointer to a struct/typedef */ 1358 t = btf__type_by_id(btf, btf_params(t)[0].type); 1359 while (t && btf_is_mod(t)) 1360 t = btf__type_by_id(btf, t->type); 1361 if (!t || !btf_is_ptr(t)) 1362 goto skip_freplace_fixup; 1363 t = btf__type_by_id(btf, t->type); 1364 while (t && btf_is_mod(t)) 1365 t = btf__type_by_id(btf, t->type); 1366 if (!t) 1367 goto skip_freplace_fixup; 1368 1369 ctx_name = btf__name_by_offset(btf, t->name_off); 1370 1371 if (guess_prog_type_by_ctx_name(ctx_name, &prog_type, &attach_type) == 0) { 1372 bpf_program__set_type(prog, prog_type); 1373 bpf_program__set_expected_attach_type(prog, attach_type); 1374 1375 if (!env.quiet) { 1376 fprintf(stderr, "Using guessed program type '%s' for %s/%s...\n", 1377 libbpf_bpf_prog_type_str(prog_type), 1378 filename, prog_name); 1379 } 1380 } else { 1381 if (!env.quiet) { 1382 fprintf(stderr, "Failed to guess program type for freplace program with context type name '%s' for %s/%s. Consider using canonical type names to help veristat...\n", 1383 ctx_name, filename, prog_name); 1384 } 1385 } 1386 } 1387 skip_freplace_fixup: 1388 return; 1389 } 1390 1391 static int max_verifier_log_size(void) 1392 { 1393 const int SMALL_LOG_SIZE = UINT_MAX >> 8; 1394 const int BIG_LOG_SIZE = UINT_MAX >> 2; 1395 struct bpf_insn insns[] = { 1396 { .code = BPF_ALU | BPF_MOV | BPF_X, .dst_reg = BPF_REG_0, }, 1397 { .code = BPF_JMP | BPF_EXIT, }, 1398 }; 1399 LIBBPF_OPTS(bpf_prog_load_opts, opts, 1400 .log_size = BIG_LOG_SIZE, 1401 .log_buf = (void *)-1, 1402 .log_level = 4 1403 ); 1404 int ret, insn_cnt = ARRAY_SIZE(insns); 1405 static int log_size; 1406 1407 if (log_size != 0) 1408 return log_size; 1409 1410 ret = bpf_prog_load(BPF_PROG_TYPE_TRACEPOINT, NULL, "GPL", insns, insn_cnt, &opts); 1411 1412 if (ret == -EFAULT) 1413 log_size = BIG_LOG_SIZE; 1414 else /* ret == -EINVAL, big log size is not supported by the verifier */ 1415 log_size = SMALL_LOG_SIZE; 1416 1417 return log_size; 1418 } 1419 1420 static bool output_stat_enabled(int id) 1421 { 1422 int i; 1423 1424 for (i = 0; i < env.output_spec.spec_cnt; i++) 1425 if (env.output_spec.ids[i] == id) 1426 return true; 1427 return false; 1428 } 1429 1430 __attribute__((format(printf, 2, 3))) 1431 static int write_one_line(const char *file, const char *fmt, ...) 1432 { 1433 int err, saved_errno; 1434 va_list ap; 1435 FILE *f; 1436 1437 f = fopen(file, "w"); 1438 if (!f) 1439 return -1; 1440 1441 va_start(ap, fmt); 1442 errno = 0; 1443 err = vfprintf(f, fmt, ap); 1444 saved_errno = errno; 1445 va_end(ap); 1446 fclose(f); 1447 errno = saved_errno; 1448 return err < 0 ? -1 : 0; 1449 } 1450 1451 __attribute__((format(scanf, 3, 4))) 1452 static int scanf_one_line(const char *file, int fields_expected, const char *fmt, ...) 1453 { 1454 int res = 0, saved_errno = 0; 1455 char *line = NULL; 1456 size_t line_len; 1457 va_list ap; 1458 FILE *f; 1459 1460 f = fopen(file, "r"); 1461 if (!f) 1462 return -1; 1463 1464 va_start(ap, fmt); 1465 while (getline(&line, &line_len, f) > 0) { 1466 res = vsscanf(line, fmt, ap); 1467 if (res == fields_expected) 1468 goto out; 1469 } 1470 if (ferror(f)) { 1471 saved_errno = errno; 1472 res = -1; 1473 } 1474 1475 out: 1476 va_end(ap); 1477 free(line); 1478 fclose(f); 1479 errno = saved_errno; 1480 return res; 1481 } 1482 1483 static void destroy_stat_cgroup(void) 1484 { 1485 char buf[PATH_MAX]; 1486 int err; 1487 1488 close(env.memory_peak_fd); 1489 1490 if (env.orig_cgroup[0]) { 1491 snprintf(buf, sizeof(buf), "%s/cgroup.procs", env.orig_cgroup); 1492 err = write_one_line(buf, "%d\n", getpid()); 1493 if (err < 0) 1494 log_errno("moving self to original cgroup %s\n", env.orig_cgroup); 1495 } 1496 1497 if (env.stat_cgroup[0]) { 1498 err = rmdir(env.stat_cgroup); 1499 if (err < 0) 1500 log_errno("deletion of cgroup %s", env.stat_cgroup); 1501 } 1502 1503 env.memory_peak_fd = -1; 1504 env.orig_cgroup[0] = 0; 1505 env.stat_cgroup[0] = 0; 1506 } 1507 1508 /* 1509 * Creates a cgroup at /sys/fs/cgroup/veristat-accounting-<pid>, 1510 * moves current process to this cgroup. 1511 */ 1512 static void create_stat_cgroup(void) 1513 { 1514 char cgroup_fs_mount[4096]; 1515 char buf[4096]; 1516 int err; 1517 1518 env.memory_peak_fd = -1; 1519 1520 if (!output_stat_enabled(MEMORY_PEAK)) 1521 return; 1522 1523 err = scanf_one_line("/proc/self/mounts", 2, "%*s %4095s cgroup2 %s", 1524 cgroup_fs_mount, buf); 1525 if (err != 2) { 1526 if (err < 0) 1527 log_errno("reading /proc/self/mounts"); 1528 else if (!env.quiet) 1529 fprintf(stderr, "Can't find cgroupfs v2 mount point.\n"); 1530 goto err_out; 1531 } 1532 1533 /* cgroup-v2.rst promises the line "0::<group>" for cgroups v2 */ 1534 err = scanf_one_line("/proc/self/cgroup", 1, "0::%4095s", buf); 1535 if (err != 1) { 1536 if (err < 0) 1537 log_errno("reading /proc/self/cgroup"); 1538 else if (!env.quiet) 1539 fprintf(stderr, "Can't infer veristat process cgroup."); 1540 goto err_out; 1541 } 1542 1543 snprintf(env.orig_cgroup, sizeof(env.orig_cgroup), "%s/%s", cgroup_fs_mount, buf); 1544 1545 snprintf(buf, sizeof(buf), "%s/veristat-accounting-%d", cgroup_fs_mount, getpid()); 1546 err = mkdir(buf, 0777); 1547 if (err < 0) { 1548 log_errno("creation of cgroup %s", buf); 1549 goto err_out; 1550 } 1551 strcpy(env.stat_cgroup, buf); 1552 1553 snprintf(buf, sizeof(buf), "%s/cgroup.procs", env.stat_cgroup); 1554 err = write_one_line(buf, "%d\n", getpid()); 1555 if (err < 0) { 1556 log_errno("entering cgroup %s", buf); 1557 goto err_out; 1558 } 1559 1560 snprintf(buf, sizeof(buf), "%s/memory.peak", env.stat_cgroup); 1561 env.memory_peak_fd = open(buf, O_RDWR | O_APPEND); 1562 if (env.memory_peak_fd < 0) { 1563 log_errno("opening %s", buf); 1564 goto err_out; 1565 } 1566 1567 return; 1568 1569 err_out: 1570 if (!env.quiet) 1571 fprintf(stderr, "Memory usage metric unavailable.\n"); 1572 destroy_stat_cgroup(); 1573 } 1574 1575 /* Current value of /sys/fs/cgroup/veristat-accounting-<pid>/memory.peak */ 1576 static long cgroup_memory_peak(void) 1577 { 1578 long err, memory_peak; 1579 char buf[32]; 1580 1581 if (env.memory_peak_fd < 0) 1582 return -1; 1583 1584 err = pread(env.memory_peak_fd, buf, sizeof(buf) - 1, 0); 1585 if (err <= 0) { 1586 log_errno("pread(%s/memory.peak)", env.stat_cgroup); 1587 return -1; 1588 } 1589 1590 buf[err] = 0; 1591 errno = 0; 1592 memory_peak = strtoll(buf, NULL, 10); 1593 if (errno) { 1594 log_errno("%s/memory.peak:strtoll(%s)", env.stat_cgroup, buf); 1595 return -1; 1596 } 1597 1598 return memory_peak; 1599 } 1600 1601 static int reset_stat_cgroup(void) 1602 { 1603 char buf[] = "r\n"; 1604 int err; 1605 1606 if (env.memory_peak_fd < 0) 1607 return -1; 1608 1609 err = pwrite(env.memory_peak_fd, buf, sizeof(buf), 0); 1610 if (err <= 0) { 1611 log_errno("pwrite(%s/memory.peak)", env.stat_cgroup); 1612 return -1; 1613 } 1614 return 0; 1615 } 1616 1617 static int parse_rvalue(const char *val, struct rvalue *rvalue) 1618 { 1619 long long value; 1620 char *val_end; 1621 1622 if (val[0] == '-' || isdigit(val[0])) { 1623 /* must be a number */ 1624 errno = 0; 1625 value = strtoll(val, &val_end, 0); 1626 if (errno == ERANGE) { 1627 errno = 0; 1628 value = strtoull(val, &val_end, 0); 1629 } 1630 if (errno || *val_end != '\0') { 1631 fprintf(stderr, "Failed to parse value '%s'\n", val); 1632 return -EINVAL; 1633 } 1634 rvalue->ivalue = value; 1635 rvalue->type = INTEGRAL; 1636 } else { 1637 /* if not a number, consider it enum value */ 1638 rvalue->svalue = strdup(val); 1639 if (!rvalue->svalue) 1640 return -ENOMEM; 1641 rvalue->type = ENUMERATOR; 1642 } 1643 return 0; 1644 } 1645 1646 static void dump(__u32 prog_id, enum dump_mode mode, const char *file_name, const char *prog_name) 1647 { 1648 char command[64], buf[4096]; 1649 FILE *fp; 1650 int status; 1651 1652 status = system("command -v bpftool > /dev/null 2>&1"); 1653 if (status != 0) { 1654 fprintf(stderr, "bpftool is not available, can't print program dump\n"); 1655 return; 1656 } 1657 snprintf(command, sizeof(command), "bpftool prog dump %s id %u", 1658 mode == DUMP_JITED ? "jited" : "xlated", prog_id); 1659 fp = popen(command, "r"); 1660 if (!fp) { 1661 fprintf(stderr, "bpftool failed with error: %d\n", errno); 1662 return; 1663 } 1664 1665 printf("DUMP (%s) %s/%s:\n", mode == DUMP_JITED ? "JITED" : "XLATED", file_name, prog_name); 1666 while (fgets(buf, sizeof(buf), fp)) 1667 fputs(buf, stdout); 1668 fprintf(stdout, "\n"); 1669 1670 if (ferror(fp)) 1671 fprintf(stderr, "Failed to dump BPF prog with error: %d\n", errno); 1672 1673 pclose(fp); 1674 } 1675 1676 static int process_prog(const char *filename, struct bpf_object *obj, struct bpf_program *prog) 1677 { 1678 const char *base_filename = basename(strdupa(filename)); 1679 const char *prog_name = bpf_program__name(prog); 1680 long mem_peak_a, mem_peak_b, mem_peak = -1; 1681 LIBBPF_OPTS(bpf_prog_load_opts, opts); 1682 char *buf; 1683 int buf_sz, log_level; 1684 struct verif_stats *stats; 1685 struct bpf_prog_info info; 1686 __u32 info_len = sizeof(info); 1687 int err = 0, cgroup_err; 1688 void *tmp; 1689 int fd; 1690 1691 if (!should_process_file_prog(base_filename, bpf_program__name(prog))) { 1692 env.progs_skipped++; 1693 return 0; 1694 } 1695 1696 tmp = realloc(env.prog_stats, (env.prog_stat_cnt + 1) * sizeof(*env.prog_stats)); 1697 if (!tmp) 1698 return -ENOMEM; 1699 env.prog_stats = tmp; 1700 stats = &env.prog_stats[env.prog_stat_cnt++]; 1701 memset(stats, 0, sizeof(*stats)); 1702 1703 if (env.verbose || env.top_src_lines > 0) { 1704 buf_sz = env.log_size ? env.log_size : max_verifier_log_size(); 1705 buf = malloc(buf_sz); 1706 if (!buf) 1707 return -ENOMEM; 1708 /* ensure we always request stats */ 1709 log_level = env.log_level | 4 | (env.log_fixed ? 8 : 0); 1710 /* --top-src-lines needs verifier log */ 1711 if (env.top_src_lines > 0 && env.log_level == 0) 1712 log_level |= 2; 1713 } else { 1714 buf = verif_log_buf; 1715 buf_sz = sizeof(verif_log_buf); 1716 /* request only verifier stats */ 1717 log_level = 4 | (env.log_fixed ? 8 : 0); 1718 } 1719 verif_log_buf[0] = '\0'; 1720 1721 /* increase chances of successful BPF object loading */ 1722 fixup_obj(obj, prog, base_filename); 1723 1724 if (env.force_checkpoints) 1725 bpf_program__set_flags(prog, bpf_program__flags(prog) | BPF_F_TEST_STATE_FREQ); 1726 if (env.force_reg_invariants) 1727 bpf_program__set_flags(prog, bpf_program__flags(prog) | BPF_F_TEST_REG_INVARIANTS); 1728 1729 opts.log_buf = buf; 1730 opts.log_size = buf_sz; 1731 opts.log_level = log_level; 1732 1733 cgroup_err = reset_stat_cgroup(); 1734 mem_peak_a = cgroup_memory_peak(); 1735 fd = bpf_program__clone(prog, &opts); 1736 if (fd < 0) { 1737 err = fd; 1738 if (env.verbose) 1739 fprintf(stderr, "Failed to load program %s %d\n", prog_name, err); 1740 } 1741 mem_peak_b = cgroup_memory_peak(); 1742 if (!cgroup_err && mem_peak_a >= 0 && mem_peak_b >= 0) 1743 mem_peak = mem_peak_b - mem_peak_a; 1744 1745 env.progs_processed++; 1746 1747 stats->file_name = strdup(base_filename); 1748 stats->prog_name = strdup(bpf_program__name(prog)); 1749 stats->stats[VERDICT] = err == 0; /* 1 - success, 0 - failure */ 1750 stats->stats[SIZE] = bpf_program__insn_cnt(prog); 1751 stats->stats[PROG_TYPE] = bpf_program__type(prog); 1752 stats->stats[ATTACH_TYPE] = bpf_program__expected_attach_type(prog); 1753 stats->stats[MEMORY_PEAK] = mem_peak < 0 ? -1 : mem_peak / (1024 * 1024); 1754 1755 memset(&info, 0, info_len); 1756 if (fd > 0 && bpf_prog_get_info_by_fd(fd, &info, &info_len) == 0) { 1757 stats->stats[JITED_SIZE] = info.jited_prog_len; 1758 if (env.dump_mode & DUMP_JITED) 1759 dump(info.id, DUMP_JITED, base_filename, prog_name); 1760 if (env.dump_mode & DUMP_XLATED) 1761 dump(info.id, DUMP_XLATED, base_filename, prog_name); 1762 } 1763 1764 parse_verif_log(buf, buf_sz, stats); 1765 1766 if (env.verbose) { 1767 printf("PROCESSING %s/%s, DURATION US: %ld, VERDICT: %s, VERIFIER LOG:\n%s\n", 1768 filename, prog_name, stats->stats[DURATION], 1769 err ? "failure" : "success", buf); 1770 } 1771 if (env.top_src_lines > 0) 1772 print_top_src_lines(buf, buf_sz, stats->prog_name); 1773 1774 if (verif_log_buf != buf) 1775 free(buf); 1776 if (fd > 0) 1777 close(fd); 1778 return 0; 1779 } 1780 1781 static int append_preset_atom(struct var_preset *preset, char *value, bool is_index) 1782 { 1783 struct field_access *tmp; 1784 int i = preset->atom_count; 1785 int err; 1786 1787 tmp = reallocarray(preset->atoms, i + 1, sizeof(*preset->atoms)); 1788 if (!tmp) 1789 return -ENOMEM; 1790 1791 preset->atoms = tmp; 1792 preset->atom_count++; 1793 1794 if (is_index) { 1795 preset->atoms[i].type = ARRAY_INDEX; 1796 err = parse_rvalue(value, &preset->atoms[i].index); 1797 if (err) 1798 return err; 1799 } else { 1800 preset->atoms[i].type = FIELD_NAME; 1801 preset->atoms[i].name = strdup(value); 1802 if (!preset->atoms[i].name) 1803 return -ENOMEM; 1804 } 1805 return 0; 1806 } 1807 1808 static int parse_var_atoms(const char *full_var, struct var_preset *preset) 1809 { 1810 char expr[256], var[256], *name, *saveptr; 1811 int n, len, off, err; 1812 1813 snprintf(expr, sizeof(expr), "%s", full_var); 1814 preset->atom_count = 0; 1815 while ((name = strtok_r(preset->atom_count ? NULL : expr, ".", &saveptr))) { 1816 len = strlen(name); 1817 /* parse variable name */ 1818 if (sscanf(name, "%[a-zA-Z0-9_] %n", var, &off) != 1) { 1819 fprintf(stderr, "Can't parse %s\n", name); 1820 return -EINVAL; 1821 } 1822 err = append_preset_atom(preset, var, false); 1823 if (err) 1824 return err; 1825 1826 /* parse optional array indexes */ 1827 while (off < len) { 1828 if (sscanf(name + off, " [ %[a-zA-Z0-9_] ] %n", var, &n) != 1) { 1829 fprintf(stderr, "Can't parse %s as index\n", name + off); 1830 return -EINVAL; 1831 } 1832 err = append_preset_atom(preset, var, true); 1833 if (err) 1834 return err; 1835 off += n; 1836 } 1837 } 1838 return 0; 1839 } 1840 1841 static int append_var_preset(struct var_preset **presets, int *cnt, const char *expr) 1842 { 1843 void *tmp; 1844 struct var_preset *cur; 1845 char var[256], val[256]; 1846 int n, err; 1847 1848 tmp = realloc(*presets, (*cnt + 1) * sizeof(**presets)); 1849 if (!tmp) 1850 return -ENOMEM; 1851 *presets = tmp; 1852 cur = &(*presets)[*cnt]; 1853 memset(cur, 0, sizeof(*cur)); 1854 (*cnt)++; 1855 1856 if (sscanf(expr, " %[][a-zA-Z0-9_. ] = %s %n", var, val, &n) != 2 || n != strlen(expr)) { 1857 fprintf(stderr, "Failed to parse expression '%s'\n", expr); 1858 return -EINVAL; 1859 } 1860 /* Remove trailing spaces from var, as scanf may add those */ 1861 rtrim(var); 1862 1863 err = parse_rvalue(val, &cur->value); 1864 if (err) 1865 return err; 1866 1867 cur->full_name = strdup(var); 1868 if (!cur->full_name) 1869 return -ENOMEM; 1870 1871 err = parse_var_atoms(var, cur); 1872 if (err) 1873 return err; 1874 1875 return 0; 1876 } 1877 1878 static int append_var_preset_file(const char *filename) 1879 { 1880 char buf[1024]; 1881 FILE *f; 1882 int err = 0; 1883 1884 f = fopen(filename, "rt"); 1885 if (!f) { 1886 err = -errno; 1887 fprintf(stderr, "Failed to open presets in '%s': %s\n", filename, strerror(-err)); 1888 return -EINVAL; 1889 } 1890 1891 while (fscanf(f, " %1023[^\n]\n", buf) == 1) { 1892 if (buf[0] == '\0' || buf[0] == '#') 1893 continue; 1894 1895 err = append_var_preset(&env.presets, &env.npresets, buf); 1896 if (err) 1897 goto cleanup; 1898 } 1899 1900 cleanup: 1901 fclose(f); 1902 return err; 1903 } 1904 1905 static bool is_signed_type(const struct btf_type *t) 1906 { 1907 if (btf_is_int(t)) 1908 return btf_int_encoding(t) & BTF_INT_SIGNED; 1909 if (btf_is_any_enum(t)) 1910 return btf_kflag(t); 1911 return true; 1912 } 1913 1914 static int enum_value_from_name(const struct btf *btf, const struct btf_type *t, 1915 const char *evalue, long long *retval) 1916 { 1917 if (btf_is_enum(t)) { 1918 struct btf_enum *e = btf_enum(t); 1919 int i, n = btf_vlen(t); 1920 1921 for (i = 0; i < n; ++i, ++e) { 1922 const char *cur_name = btf__name_by_offset(btf, e->name_off); 1923 1924 if (strcmp(cur_name, evalue) == 0) { 1925 *retval = e->val; 1926 return 0; 1927 } 1928 } 1929 } else if (btf_is_enum64(t)) { 1930 struct btf_enum64 *e = btf_enum64(t); 1931 int i, n = btf_vlen(t); 1932 1933 for (i = 0; i < n; ++i, ++e) { 1934 const char *cur_name = btf__name_by_offset(btf, e->name_off); 1935 __u64 value = btf_enum64_value(e); 1936 1937 if (strcmp(cur_name, evalue) == 0) { 1938 *retval = value; 1939 return 0; 1940 } 1941 } 1942 } 1943 return -EINVAL; 1944 } 1945 1946 static bool is_preset_supported(const struct btf_type *t) 1947 { 1948 return btf_is_int(t) || btf_is_enum(t) || btf_is_enum64(t); 1949 } 1950 1951 static int find_enum_value(const struct btf *btf, const char *name, long long *value) 1952 { 1953 const struct btf_type *t; 1954 int cnt, i; 1955 long long lvalue; 1956 1957 cnt = btf__type_cnt(btf); 1958 for (i = 1; i != cnt; ++i) { 1959 t = btf__type_by_id(btf, i); 1960 1961 if (!btf_is_any_enum(t)) 1962 continue; 1963 1964 if (enum_value_from_name(btf, t, name, &lvalue) == 0) { 1965 *value = lvalue; 1966 return 0; 1967 } 1968 } 1969 return -ESRCH; 1970 } 1971 1972 static int resolve_rvalue(struct btf *btf, const struct rvalue *rvalue, long long *result) 1973 { 1974 int err = 0; 1975 1976 switch (rvalue->type) { 1977 case INTEGRAL: 1978 *result = rvalue->ivalue; 1979 return 0; 1980 case ENUMERATOR: 1981 err = find_enum_value(btf, rvalue->svalue, result); 1982 if (err) { 1983 fprintf(stderr, "Can't resolve enum value %s\n", rvalue->svalue); 1984 return err; 1985 } 1986 return 0; 1987 default: 1988 fprintf(stderr, "Unknown rvalue type\n"); 1989 return -EOPNOTSUPP; 1990 } 1991 return 0; 1992 } 1993 1994 static int adjust_var_secinfo_array(struct btf *btf, int tid, struct field_access *atom, 1995 const char *array_name, struct btf_var_secinfo *sinfo) 1996 { 1997 const struct btf_type *t; 1998 struct btf_array *barr; 1999 long long idx; 2000 int err; 2001 2002 tid = btf__resolve_type(btf, tid); 2003 t = btf__type_by_id(btf, tid); 2004 if (!btf_is_array(t)) { 2005 fprintf(stderr, "Array index is not expected for %s\n", 2006 array_name); 2007 return -EINVAL; 2008 } 2009 barr = btf_array(t); 2010 err = resolve_rvalue(btf, &atom->index, &idx); 2011 if (err) 2012 return err; 2013 if (idx < 0 || idx >= barr->nelems) { 2014 fprintf(stderr, "Array index %lld is out of bounds [0, %u): %s\n", 2015 idx, barr->nelems, array_name); 2016 return -EINVAL; 2017 } 2018 sinfo->size = btf__resolve_size(btf, barr->type); 2019 sinfo->offset += sinfo->size * idx; 2020 sinfo->type = btf__resolve_type(btf, barr->type); 2021 return 0; 2022 } 2023 2024 static int adjust_var_secinfo_member(const struct btf *btf, 2025 const struct btf_type *parent_type, 2026 __u32 parent_offset, 2027 const char *member_name, 2028 struct btf_var_secinfo *sinfo) 2029 { 2030 int i; 2031 2032 if (!btf_is_composite(parent_type)) { 2033 fprintf(stderr, "Can't resolve field %s for non-composite type\n", member_name); 2034 return -EINVAL; 2035 } 2036 2037 for (i = 0; i < btf_vlen(parent_type); ++i) { 2038 const struct btf_member *member; 2039 const struct btf_type *member_type; 2040 int tid, off; 2041 2042 member = btf_members(parent_type) + i; 2043 tid = btf__resolve_type(btf, member->type); 2044 if (tid < 0) 2045 return -EINVAL; 2046 2047 member_type = btf__type_by_id(btf, tid); 2048 off = parent_offset + member->offset; 2049 if (member->name_off) { 2050 const char *name = btf__name_by_offset(btf, member->name_off); 2051 2052 if (strcmp(member_name, name) == 0) { 2053 if (btf_member_bitfield_size(parent_type, i) != 0) { 2054 fprintf(stderr, "Bitfield presets are not supported %s\n", 2055 name); 2056 return -EINVAL; 2057 } 2058 sinfo->offset += off / 8; 2059 sinfo->type = tid; 2060 sinfo->size = member_type->size; 2061 return 0; 2062 } 2063 } else if (btf_is_composite(member_type)) { 2064 int err; 2065 2066 err = adjust_var_secinfo_member(btf, member_type, off, 2067 member_name, sinfo); 2068 if (!err) 2069 return 0; 2070 } 2071 } 2072 2073 return -ESRCH; 2074 } 2075 2076 static int adjust_var_secinfo(struct btf *btf, const struct btf_type *t, 2077 struct btf_var_secinfo *sinfo, struct var_preset *preset) 2078 { 2079 const struct btf_type *base_type; 2080 const char *prev_name; 2081 int err, i; 2082 int tid; 2083 2084 assert(preset->atom_count > 0); 2085 assert(preset->atoms[0].type == FIELD_NAME); 2086 2087 tid = btf__resolve_type(btf, t->type); 2088 base_type = btf__type_by_id(btf, tid); 2089 prev_name = preset->atoms[0].name; 2090 2091 for (i = 1; i < preset->atom_count; ++i) { 2092 struct field_access *atom = preset->atoms + i; 2093 2094 switch (atom->type) { 2095 case ARRAY_INDEX: 2096 err = adjust_var_secinfo_array(btf, tid, atom, prev_name, sinfo); 2097 break; 2098 case FIELD_NAME: 2099 err = adjust_var_secinfo_member(btf, base_type, 0, atom->name, sinfo); 2100 if (err == -ESRCH) 2101 fprintf(stderr, "Can't find '%s'\n", atom->name); 2102 prev_name = atom->name; 2103 break; 2104 default: 2105 fprintf(stderr, "Unknown field_access type\n"); 2106 return -EOPNOTSUPP; 2107 } 2108 if (err) 2109 return err; 2110 base_type = btf__type_by_id(btf, sinfo->type); 2111 tid = sinfo->type; 2112 } 2113 2114 return 0; 2115 } 2116 2117 static int set_global_var(struct bpf_object *obj, struct btf *btf, 2118 struct bpf_map *map, struct btf_var_secinfo *sinfo, 2119 struct var_preset *preset) 2120 { 2121 const struct btf_type *base_type; 2122 void *ptr; 2123 long long value = preset->value.ivalue; 2124 size_t size; 2125 2126 base_type = btf__type_by_id(btf, btf__resolve_type(btf, sinfo->type)); 2127 if (!base_type) { 2128 fprintf(stderr, "Failed to resolve type %d\n", sinfo->type); 2129 return -EINVAL; 2130 } 2131 if (!is_preset_supported(base_type)) { 2132 fprintf(stderr, "Can't set %s. Only ints and enums are supported\n", 2133 preset->full_name); 2134 return -EINVAL; 2135 } 2136 2137 if (preset->value.type == ENUMERATOR) { 2138 if (btf_is_any_enum(base_type)) { 2139 if (enum_value_from_name(btf, base_type, preset->value.svalue, &value)) { 2140 fprintf(stderr, 2141 "Failed to find integer value for enum element %s\n", 2142 preset->value.svalue); 2143 return -EINVAL; 2144 } 2145 } else { 2146 fprintf(stderr, "Value %s is not supported for type %s\n", 2147 preset->value.svalue, 2148 btf__name_by_offset(btf, base_type->name_off)); 2149 return -EINVAL; 2150 } 2151 } 2152 2153 /* Check if value fits into the target variable size */ 2154 if (sinfo->size < sizeof(value)) { 2155 bool is_signed = is_signed_type(base_type); 2156 __u32 unsigned_bits = sinfo->size * 8 - (is_signed ? 1 : 0); 2157 long long max_val = 1ll << unsigned_bits; 2158 2159 if (value >= max_val || value < -max_val) { 2160 fprintf(stderr, 2161 "Variable %s value %lld is out of range [%lld; %lld]\n", 2162 btf__name_by_offset(btf, base_type->name_off), value, 2163 is_signed ? -max_val : 0, max_val - 1); 2164 return -EINVAL; 2165 } 2166 } 2167 2168 ptr = bpf_map__initial_value(map, &size); 2169 if (!ptr || sinfo->offset + sinfo->size > size) 2170 return -EINVAL; 2171 2172 if (__BYTE_ORDER == __LITTLE_ENDIAN) { 2173 memcpy(ptr + sinfo->offset, &value, sinfo->size); 2174 } else { /* __BYTE_ORDER == __BIG_ENDIAN */ 2175 __u8 src_offset = sizeof(value) - sinfo->size; 2176 2177 memcpy(ptr + sinfo->offset, (void *)&value + src_offset, sinfo->size); 2178 } 2179 return 0; 2180 } 2181 2182 static int set_global_vars(struct bpf_object *obj, struct var_preset *presets, int npresets) 2183 { 2184 struct btf_var_secinfo *sinfo; 2185 const char *sec_name; 2186 const struct btf_type *t; 2187 struct bpf_map *map; 2188 struct btf *btf; 2189 int i, j, k, n, cnt, err = 0; 2190 2191 if (npresets == 0) 2192 return 0; 2193 2194 btf = bpf_object__btf(obj); 2195 if (!btf) 2196 return -EINVAL; 2197 2198 cnt = btf__type_cnt(btf); 2199 for (i = 1; i != cnt; ++i) { 2200 t = btf__type_by_id(btf, i); 2201 2202 if (!btf_is_datasec(t)) 2203 continue; 2204 2205 sinfo = btf_var_secinfos(t); 2206 sec_name = btf__name_by_offset(btf, t->name_off); 2207 map = bpf_object__find_map_by_name(obj, sec_name); 2208 if (!map) 2209 continue; 2210 2211 n = btf_vlen(t); 2212 for (j = 0; j < n; ++j, ++sinfo) { 2213 const struct btf_type *var_type = btf__type_by_id(btf, sinfo->type); 2214 const char *var_name; 2215 2216 if (!btf_is_var(var_type)) 2217 continue; 2218 2219 var_name = btf__name_by_offset(btf, var_type->name_off); 2220 2221 for (k = 0; k < npresets; ++k) { 2222 struct btf_var_secinfo tmp_sinfo; 2223 2224 if (strcmp(var_name, presets[k].atoms[0].name) != 0) 2225 continue; 2226 2227 if (presets[k].applied) { 2228 fprintf(stderr, "Variable %s is set more than once", 2229 var_name); 2230 return -EINVAL; 2231 } 2232 tmp_sinfo = *sinfo; 2233 err = adjust_var_secinfo(btf, var_type, 2234 &tmp_sinfo, presets + k); 2235 if (err) 2236 return err; 2237 2238 err = set_global_var(obj, btf, map, &tmp_sinfo, presets + k); 2239 if (err) 2240 return err; 2241 2242 presets[k].applied = true; 2243 } 2244 } 2245 } 2246 for (i = 0; i < npresets; ++i) { 2247 if (!presets[i].applied) { 2248 fprintf(stderr, "Global variable preset %s has not been applied\n", 2249 presets[i].full_name); 2250 err = -EINVAL; 2251 } 2252 presets[i].applied = false; 2253 } 2254 return err; 2255 } 2256 2257 static int process_obj(const char *filename) 2258 { 2259 const char *base_filename = basename(strdupa(filename)); 2260 struct bpf_object *obj = NULL; 2261 struct bpf_program *prog; 2262 libbpf_print_fn_t old_libbpf_print_fn; 2263 LIBBPF_OPTS(bpf_object_open_opts, opts); 2264 int err = 0, prog_cnt = 0; 2265 2266 if (!should_process_file_prog(base_filename, NULL)) { 2267 if (env.verbose) 2268 printf("Skipping '%s' due to filters...\n", filename); 2269 env.files_skipped++; 2270 return 0; 2271 } 2272 if (!is_bpf_obj_file(filename)) { 2273 if (env.verbose) 2274 printf("Skipping '%s' as it's not a BPF object file...\n", filename); 2275 env.files_skipped++; 2276 return 0; 2277 } 2278 2279 if (!env.quiet && env.out_fmt == RESFMT_TABLE) 2280 printf("Processing '%s'...\n", base_filename); 2281 2282 old_libbpf_print_fn = libbpf_set_print(libbpf_print_fn); 2283 obj = bpf_object__open_file(filename, &opts); 2284 if (!obj) { 2285 /* if libbpf can't open BPF object file, it could be because 2286 * that BPF object file is incomplete and has to be statically 2287 * linked into a final BPF object file; instead of bailing 2288 * out, report it into stderr, mark it as skipped, and 2289 * proceed 2290 */ 2291 fprintf(stderr, "Failed to open '%s': %d\n", filename, -errno); 2292 env.files_skipped++; 2293 err = 0; 2294 goto cleanup; 2295 } 2296 2297 env.files_processed++; 2298 2299 bpf_object__for_each_program(prog, obj) { 2300 bpf_program__set_autoload(prog, true); 2301 prog_cnt++; 2302 } 2303 2304 fixup_obj_maps(obj); 2305 2306 err = set_global_vars(obj, env.presets, env.npresets); 2307 if (err) { 2308 fprintf(stderr, "Failed to set global variables %d\n", err); 2309 goto cleanup; 2310 } 2311 2312 err = bpf_object__prepare(obj); 2313 if (err && env.verbose) /* run process_prog() anyway to output per program failures */ 2314 fprintf(stderr, "Failed to prepare BPF object for loading %d\n", err); 2315 2316 bpf_object__for_each_program(prog, obj) { 2317 process_prog(filename, obj, prog); 2318 } 2319 2320 cleanup: 2321 bpf_object__close(obj); 2322 libbpf_set_print(old_libbpf_print_fn); 2323 return err; 2324 } 2325 2326 static int cmp_stat(const struct verif_stats *s1, const struct verif_stats *s2, 2327 enum stat_id id, bool asc, bool abs) 2328 { 2329 int cmp = 0; 2330 2331 switch (id) { 2332 case FILE_NAME: 2333 cmp = strcmp(s1->file_name, s2->file_name); 2334 break; 2335 case PROG_NAME: 2336 cmp = strcmp(s1->prog_name, s2->prog_name); 2337 break; 2338 case ATTACH_TYPE: 2339 case PROG_TYPE: 2340 case SIZE: 2341 case JITED_SIZE: 2342 case STACK: 2343 case MAX_STACK: 2344 case VERDICT: 2345 case DURATION: 2346 case TOTAL_INSNS: 2347 case TOTAL_STATES: 2348 case PEAK_STATES: 2349 case MAX_STATES_PER_INSN: 2350 case MEMORY_PEAK: 2351 case MARK_READ_MAX_LEN: { 2352 long v1 = s1->stats[id]; 2353 long v2 = s2->stats[id]; 2354 2355 if (abs) { 2356 v1 = v1 < 0 ? -v1 : v1; 2357 v2 = v2 < 0 ? -v2 : v2; 2358 } 2359 2360 if (v1 != v2) 2361 cmp = v1 < v2 ? -1 : 1; 2362 break; 2363 } 2364 default: 2365 fprintf(stderr, "Unrecognized stat #%d\n", id); 2366 exit(1); 2367 } 2368 2369 return asc ? cmp : -cmp; 2370 } 2371 2372 static int cmp_prog_stats(const void *v1, const void *v2) 2373 { 2374 const struct verif_stats *s1 = v1, *s2 = v2; 2375 int i, cmp; 2376 2377 for (i = 0; i < env.sort_spec.spec_cnt; i++) { 2378 cmp = cmp_stat(s1, s2, env.sort_spec.ids[i], 2379 env.sort_spec.asc[i], env.sort_spec.abs[i]); 2380 if (cmp != 0) 2381 return cmp; 2382 } 2383 2384 /* always disambiguate with file+prog, which are unique */ 2385 cmp = strcmp(s1->file_name, s2->file_name); 2386 if (cmp != 0) 2387 return cmp; 2388 return strcmp(s1->prog_name, s2->prog_name); 2389 } 2390 2391 static void fetch_join_stat_value(const struct verif_stats_join *s, 2392 enum stat_id id, enum stat_variant var, 2393 const char **str_val, 2394 double *num_val) 2395 { 2396 long v1, v2; 2397 2398 if (id == FILE_NAME) { 2399 *str_val = s->file_name; 2400 return; 2401 } 2402 if (id == PROG_NAME) { 2403 *str_val = s->prog_name; 2404 return; 2405 } 2406 2407 v1 = s->stats_a ? s->stats_a->stats[id] : 0; 2408 v2 = s->stats_b ? s->stats_b->stats[id] : 0; 2409 2410 switch (var) { 2411 case VARIANT_A: 2412 if (!s->stats_a) 2413 *num_val = -DBL_MAX; 2414 else 2415 *num_val = s->stats_a->stats[id]; 2416 return; 2417 case VARIANT_B: 2418 if (!s->stats_b) 2419 *num_val = -DBL_MAX; 2420 else 2421 *num_val = s->stats_b->stats[id]; 2422 return; 2423 case VARIANT_DIFF: 2424 if (!s->stats_a || !s->stats_b) 2425 *num_val = -DBL_MAX; 2426 else if (id == VERDICT) 2427 *num_val = v1 == v2 ? 1.0 /* MATCH */ : 0.0 /* MISMATCH */; 2428 else 2429 *num_val = (double)(v2 - v1); 2430 return; 2431 case VARIANT_PCT: 2432 if (!s->stats_a || !s->stats_b) { 2433 *num_val = -DBL_MAX; 2434 } else if (v1 == 0) { 2435 if (v1 == v2) 2436 *num_val = 0.0; 2437 else 2438 *num_val = v2 < v1 ? -100.0 : 100.0; 2439 } else { 2440 *num_val = (v2 - v1) * 100.0 / v1; 2441 } 2442 return; 2443 } 2444 } 2445 2446 static int cmp_join_stat(const struct verif_stats_join *s1, 2447 const struct verif_stats_join *s2, 2448 enum stat_id id, enum stat_variant var, 2449 bool asc, bool abs) 2450 { 2451 const char *str1 = NULL, *str2 = NULL; 2452 double v1 = 0.0, v2 = 0.0; 2453 int cmp = 0; 2454 2455 fetch_join_stat_value(s1, id, var, &str1, &v1); 2456 fetch_join_stat_value(s2, id, var, &str2, &v2); 2457 2458 if (abs) { 2459 v1 = fabs(v1); 2460 v2 = fabs(v2); 2461 } 2462 2463 if (str1) 2464 cmp = strcmp(str1, str2); 2465 else if (v1 != v2) 2466 cmp = v1 < v2 ? -1 : 1; 2467 2468 return asc ? cmp : -cmp; 2469 } 2470 2471 static int cmp_join_stats(const void *v1, const void *v2) 2472 { 2473 const struct verif_stats_join *s1 = v1, *s2 = v2; 2474 int i, cmp; 2475 2476 for (i = 0; i < env.sort_spec.spec_cnt; i++) { 2477 cmp = cmp_join_stat(s1, s2, 2478 env.sort_spec.ids[i], 2479 env.sort_spec.variants[i], 2480 env.sort_spec.asc[i], 2481 env.sort_spec.abs[i]); 2482 if (cmp != 0) 2483 return cmp; 2484 } 2485 2486 /* always disambiguate with file+prog, which are unique */ 2487 cmp = strcmp(s1->file_name, s2->file_name); 2488 if (cmp != 0) 2489 return cmp; 2490 return strcmp(s1->prog_name, s2->prog_name); 2491 } 2492 2493 #define HEADER_CHAR '-' 2494 #define COLUMN_SEP " " 2495 2496 static void output_header_underlines(void) 2497 { 2498 int i, j, len; 2499 2500 for (i = 0; i < env.output_spec.spec_cnt; i++) { 2501 len = env.output_spec.lens[i]; 2502 2503 printf("%s", i == 0 ? "" : COLUMN_SEP); 2504 for (j = 0; j < len; j++) 2505 printf("%c", HEADER_CHAR); 2506 } 2507 printf("\n"); 2508 } 2509 2510 static void output_headers(enum resfmt fmt) 2511 { 2512 const char *fmt_str; 2513 int i, len; 2514 2515 for (i = 0; i < env.output_spec.spec_cnt; i++) { 2516 int id = env.output_spec.ids[i]; 2517 int *max_len = &env.output_spec.lens[i]; 2518 2519 switch (fmt) { 2520 case RESFMT_TABLE_CALCLEN: 2521 len = snprintf(NULL, 0, "%s", stat_defs[id].header); 2522 if (len > *max_len) 2523 *max_len = len; 2524 break; 2525 case RESFMT_TABLE: 2526 fmt_str = stat_defs[id].left_aligned ? "%s%-*s" : "%s%*s"; 2527 printf(fmt_str, i == 0 ? "" : COLUMN_SEP, *max_len, stat_defs[id].header); 2528 if (i == env.output_spec.spec_cnt - 1) 2529 printf("\n"); 2530 break; 2531 case RESFMT_CSV: 2532 printf("%s%s", i == 0 ? "" : ",", stat_defs[id].names[0]); 2533 if (i == env.output_spec.spec_cnt - 1) 2534 printf("\n"); 2535 break; 2536 } 2537 } 2538 2539 if (fmt == RESFMT_TABLE) 2540 output_header_underlines(); 2541 } 2542 2543 static void prepare_value(const struct verif_stats *s, enum stat_id id, 2544 const char **str, long *val) 2545 { 2546 switch (id) { 2547 case FILE_NAME: 2548 *str = s ? s->file_name : "N/A"; 2549 break; 2550 case PROG_NAME: 2551 *str = s ? s->prog_name : "N/A"; 2552 break; 2553 case VERDICT: 2554 if (!s) 2555 *str = "N/A"; 2556 else 2557 *str = s->stats[VERDICT] ? "success" : "failure"; 2558 break; 2559 case ATTACH_TYPE: 2560 if (!s) 2561 *str = "N/A"; 2562 else 2563 *str = libbpf_bpf_attach_type_str(s->stats[ATTACH_TYPE]) ?: "N/A"; 2564 break; 2565 case PROG_TYPE: 2566 if (!s) 2567 *str = "N/A"; 2568 else 2569 *str = libbpf_bpf_prog_type_str(s->stats[PROG_TYPE]) ?: "N/A"; 2570 break; 2571 case DURATION: 2572 case TOTAL_INSNS: 2573 case TOTAL_STATES: 2574 case PEAK_STATES: 2575 case MAX_STATES_PER_INSN: 2576 case MARK_READ_MAX_LEN: 2577 case STACK: 2578 case MAX_STACK: 2579 case SIZE: 2580 case JITED_SIZE: 2581 case MEMORY_PEAK: 2582 *val = s ? s->stats[id] : 0; 2583 break; 2584 default: 2585 fprintf(stderr, "Unrecognized stat #%d\n", id); 2586 exit(1); 2587 } 2588 } 2589 2590 static void output_stats(const struct verif_stats *s, enum resfmt fmt, bool last) 2591 { 2592 int i; 2593 2594 for (i = 0; i < env.output_spec.spec_cnt; i++) { 2595 int id = env.output_spec.ids[i]; 2596 int *max_len = &env.output_spec.lens[i], len; 2597 const char *str = NULL; 2598 long val = 0; 2599 2600 prepare_value(s, id, &str, &val); 2601 2602 switch (fmt) { 2603 case RESFMT_TABLE_CALCLEN: 2604 if (str) 2605 len = snprintf(NULL, 0, "%s", str); 2606 else 2607 len = snprintf(NULL, 0, "%ld", val); 2608 if (len > *max_len) 2609 *max_len = len; 2610 break; 2611 case RESFMT_TABLE: 2612 if (str) 2613 printf("%s%-*s", i == 0 ? "" : COLUMN_SEP, *max_len, str); 2614 else 2615 printf("%s%*ld", i == 0 ? "" : COLUMN_SEP, *max_len, val); 2616 if (i == env.output_spec.spec_cnt - 1) 2617 printf("\n"); 2618 break; 2619 case RESFMT_CSV: 2620 if (str) 2621 printf("%s%s", i == 0 ? "" : ",", str); 2622 else 2623 printf("%s%ld", i == 0 ? "" : ",", val); 2624 if (i == env.output_spec.spec_cnt - 1) 2625 printf("\n"); 2626 break; 2627 } 2628 } 2629 2630 if (last && fmt == RESFMT_TABLE) { 2631 output_header_underlines(); 2632 printf("Done. Processed %d files, %d programs. Skipped %d files, %d programs.\n", 2633 env.files_processed, env.progs_processed, env.files_skipped, env.progs_skipped); 2634 } 2635 } 2636 2637 static int parse_stat_value(const char *str, enum stat_id id, struct verif_stats *st) 2638 { 2639 switch (id) { 2640 case FILE_NAME: 2641 st->file_name = strdup(str); 2642 if (!st->file_name) 2643 return -ENOMEM; 2644 break; 2645 case PROG_NAME: 2646 st->prog_name = strdup(str); 2647 if (!st->prog_name) 2648 return -ENOMEM; 2649 break; 2650 case VERDICT: 2651 if (strcmp(str, "success") == 0) { 2652 st->stats[VERDICT] = true; 2653 } else if (strcmp(str, "failure") == 0) { 2654 st->stats[VERDICT] = false; 2655 } else { 2656 fprintf(stderr, "Unrecognized verification verdict '%s'\n", str); 2657 return -EINVAL; 2658 } 2659 break; 2660 case DURATION: 2661 case TOTAL_INSNS: 2662 case TOTAL_STATES: 2663 case PEAK_STATES: 2664 case MAX_STATES_PER_INSN: 2665 case MARK_READ_MAX_LEN: 2666 case SIZE: 2667 case JITED_SIZE: 2668 case MEMORY_PEAK: 2669 case STACK: 2670 case MAX_STACK: { 2671 long val; 2672 int err, n; 2673 2674 if (sscanf(str, "%ld %n", &val, &n) != 1 || n != strlen(str)) { 2675 err = -errno; 2676 fprintf(stderr, "Failed to parse '%s' as integer\n", str); 2677 return err; 2678 } 2679 2680 st->stats[id] = val; 2681 break; 2682 } 2683 case PROG_TYPE: { 2684 enum bpf_prog_type prog_type = 0; 2685 const char *type; 2686 2687 while ((type = libbpf_bpf_prog_type_str(prog_type))) { 2688 if (strcmp(type, str) == 0) { 2689 st->stats[id] = prog_type; 2690 break; 2691 } 2692 prog_type++; 2693 } 2694 2695 if (!type) { 2696 fprintf(stderr, "Unrecognized prog type %s\n", str); 2697 return -EINVAL; 2698 } 2699 break; 2700 } 2701 case ATTACH_TYPE: { 2702 enum bpf_attach_type attach_type = 0; 2703 const char *type; 2704 2705 while ((type = libbpf_bpf_attach_type_str(attach_type))) { 2706 if (strcmp(type, str) == 0) { 2707 st->stats[id] = attach_type; 2708 break; 2709 } 2710 attach_type++; 2711 } 2712 2713 if (!type) { 2714 fprintf(stderr, "Unrecognized attach type %s\n", str); 2715 return -EINVAL; 2716 } 2717 break; 2718 } 2719 default: 2720 fprintf(stderr, "Unrecognized stat #%d\n", id); 2721 return -EINVAL; 2722 } 2723 return 0; 2724 } 2725 2726 static int parse_stats_csv(const char *filename, struct stat_specs *specs, 2727 struct verif_stats **statsp, int *stat_cntp) 2728 { 2729 char line[4096]; 2730 FILE *f; 2731 int err = 0; 2732 bool header = true; 2733 2734 f = fopen(filename, "r"); 2735 if (!f) { 2736 err = -errno; 2737 fprintf(stderr, "Failed to open '%s': %d\n", filename, err); 2738 return err; 2739 } 2740 2741 *stat_cntp = 0; 2742 2743 while (fgets(line, sizeof(line), f)) { 2744 char *input = line, *state = NULL, *next; 2745 struct verif_stats *st = NULL; 2746 int col = 0, cnt = 0; 2747 2748 if (!header) { 2749 void *tmp; 2750 2751 tmp = realloc(*statsp, (*stat_cntp + 1) * sizeof(**statsp)); 2752 if (!tmp) { 2753 err = -ENOMEM; 2754 goto cleanup; 2755 } 2756 *statsp = tmp; 2757 2758 st = &(*statsp)[*stat_cntp]; 2759 memset(st, 0, sizeof(*st)); 2760 2761 *stat_cntp += 1; 2762 } 2763 2764 while ((next = strtok_r(cnt++ ? NULL : input, ",\n", &state))) { 2765 if (header) { 2766 /* for the first line, set up spec stats */ 2767 err = parse_stat(next, specs); 2768 if (err) 2769 goto cleanup; 2770 continue; 2771 } 2772 2773 /* for all other lines, parse values based on spec */ 2774 if (col >= specs->spec_cnt) { 2775 fprintf(stderr, "Found extraneous column #%d in row #%d of '%s'\n", 2776 col, *stat_cntp, filename); 2777 err = -EINVAL; 2778 goto cleanup; 2779 } 2780 err = parse_stat_value(next, specs->ids[col], st); 2781 if (err) 2782 goto cleanup; 2783 col++; 2784 } 2785 2786 if (header) { 2787 header = false; 2788 continue; 2789 } 2790 2791 if (col < specs->spec_cnt) { 2792 fprintf(stderr, "Not enough columns in row #%d in '%s'\n", 2793 *stat_cntp, filename); 2794 err = -EINVAL; 2795 goto cleanup; 2796 } 2797 2798 if (!st->file_name || !st->prog_name) { 2799 fprintf(stderr, "Row #%d in '%s' is missing file and/or program name\n", 2800 *stat_cntp, filename); 2801 err = -EINVAL; 2802 goto cleanup; 2803 } 2804 2805 /* in comparison mode we can only check filters after we 2806 * parsed entire line; if row should be ignored we pretend we 2807 * never parsed it 2808 */ 2809 if (!should_process_file_prog(st->file_name, st->prog_name)) { 2810 free(st->file_name); 2811 free(st->prog_name); 2812 *stat_cntp -= 1; 2813 } 2814 } 2815 2816 if (!feof(f)) { 2817 err = -errno; 2818 fprintf(stderr, "Failed I/O for '%s': %d\n", filename, err); 2819 } 2820 2821 cleanup: 2822 fclose(f); 2823 return err; 2824 } 2825 2826 /* empty/zero stats for mismatched rows */ 2827 static const struct verif_stats fallback_stats = { .file_name = "", .prog_name = "" }; 2828 2829 static bool is_key_stat(enum stat_id id) 2830 { 2831 return id == FILE_NAME || id == PROG_NAME; 2832 } 2833 2834 static void output_comp_header_underlines(void) 2835 { 2836 int i, j, k; 2837 2838 for (i = 0; i < env.output_spec.spec_cnt; i++) { 2839 int id = env.output_spec.ids[i]; 2840 int max_j = is_key_stat(id) ? 1 : 3; 2841 2842 for (j = 0; j < max_j; j++) { 2843 int len = env.output_spec.lens[3 * i + j]; 2844 2845 printf("%s", i + j == 0 ? "" : COLUMN_SEP); 2846 2847 for (k = 0; k < len; k++) 2848 printf("%c", HEADER_CHAR); 2849 } 2850 } 2851 printf("\n"); 2852 } 2853 2854 static void output_comp_headers(enum resfmt fmt) 2855 { 2856 static const char *table_sfxs[3] = {" (A)", " (B)", " (DIFF)"}; 2857 static const char *name_sfxs[3] = {"_base", "_comp", "_diff"}; 2858 int i, j, len; 2859 2860 for (i = 0; i < env.output_spec.spec_cnt; i++) { 2861 int id = env.output_spec.ids[i]; 2862 /* key stats don't have A/B/DIFF columns, they are common for both data sets */ 2863 int max_j = is_key_stat(id) ? 1 : 3; 2864 2865 for (j = 0; j < max_j; j++) { 2866 int *max_len = &env.output_spec.lens[3 * i + j]; 2867 bool last = (i == env.output_spec.spec_cnt - 1) && (j == max_j - 1); 2868 const char *sfx; 2869 2870 switch (fmt) { 2871 case RESFMT_TABLE_CALCLEN: 2872 sfx = is_key_stat(id) ? "" : table_sfxs[j]; 2873 len = snprintf(NULL, 0, "%s%s", stat_defs[id].header, sfx); 2874 if (len > *max_len) 2875 *max_len = len; 2876 break; 2877 case RESFMT_TABLE: 2878 sfx = is_key_stat(id) ? "" : table_sfxs[j]; 2879 printf("%s%-*s%s", i + j == 0 ? "" : COLUMN_SEP, 2880 *max_len - (int)strlen(sfx), stat_defs[id].header, sfx); 2881 if (last) 2882 printf("\n"); 2883 break; 2884 case RESFMT_CSV: 2885 sfx = is_key_stat(id) ? "" : name_sfxs[j]; 2886 printf("%s%s%s", i + j == 0 ? "" : ",", stat_defs[id].names[0], sfx); 2887 if (last) 2888 printf("\n"); 2889 break; 2890 } 2891 } 2892 } 2893 2894 if (fmt == RESFMT_TABLE) 2895 output_comp_header_underlines(); 2896 } 2897 2898 static void output_comp_stats(const struct verif_stats_join *join_stats, 2899 enum resfmt fmt, bool last) 2900 { 2901 const struct verif_stats *base = join_stats->stats_a; 2902 const struct verif_stats *comp = join_stats->stats_b; 2903 char base_buf[1024] = {}, comp_buf[1024] = {}, diff_buf[1024] = {}; 2904 int i; 2905 2906 for (i = 0; i < env.output_spec.spec_cnt; i++) { 2907 int id = env.output_spec.ids[i], len; 2908 int *max_len_base = &env.output_spec.lens[3 * i + 0]; 2909 int *max_len_comp = &env.output_spec.lens[3 * i + 1]; 2910 int *max_len_diff = &env.output_spec.lens[3 * i + 2]; 2911 const char *base_str = NULL, *comp_str = NULL; 2912 long base_val = 0, comp_val = 0, diff_val = 0; 2913 2914 prepare_value(base, id, &base_str, &base_val); 2915 prepare_value(comp, id, &comp_str, &comp_val); 2916 2917 /* normalize all the outputs to be in string buffers for simplicity */ 2918 if (is_key_stat(id)) { 2919 /* key stats (file and program name) are always strings */ 2920 if (base) 2921 snprintf(base_buf, sizeof(base_buf), "%s", base_str); 2922 else 2923 snprintf(base_buf, sizeof(base_buf), "%s", comp_str); 2924 } else if (base_str) { 2925 snprintf(base_buf, sizeof(base_buf), "%s", base_str); 2926 snprintf(comp_buf, sizeof(comp_buf), "%s", comp_str); 2927 if (!base || !comp) 2928 snprintf(diff_buf, sizeof(diff_buf), "%s", "N/A"); 2929 else if (strcmp(base_str, comp_str) == 0) 2930 snprintf(diff_buf, sizeof(diff_buf), "%s", "MATCH"); 2931 else 2932 snprintf(diff_buf, sizeof(diff_buf), "%s", "MISMATCH"); 2933 } else { 2934 double p = 0.0; 2935 2936 if (base) 2937 snprintf(base_buf, sizeof(base_buf), "%ld", base_val); 2938 else 2939 snprintf(base_buf, sizeof(base_buf), "%s", "N/A"); 2940 if (comp) 2941 snprintf(comp_buf, sizeof(comp_buf), "%ld", comp_val); 2942 else 2943 snprintf(comp_buf, sizeof(comp_buf), "%s", "N/A"); 2944 2945 diff_val = comp_val - base_val; 2946 if (!base || !comp) { 2947 snprintf(diff_buf, sizeof(diff_buf), "%s", "N/A"); 2948 } else { 2949 if (base_val == 0) { 2950 if (comp_val == base_val) 2951 p = 0.0; /* avoid +0 (+100%) case */ 2952 else 2953 p = comp_val < base_val ? -100.0 : 100.0; 2954 } else { 2955 p = diff_val * 100.0 / base_val; 2956 } 2957 snprintf(diff_buf, sizeof(diff_buf), "%+ld (%+.2lf%%)", diff_val, p); 2958 } 2959 } 2960 2961 switch (fmt) { 2962 case RESFMT_TABLE_CALCLEN: 2963 len = strlen(base_buf); 2964 if (len > *max_len_base) 2965 *max_len_base = len; 2966 if (!is_key_stat(id)) { 2967 len = strlen(comp_buf); 2968 if (len > *max_len_comp) 2969 *max_len_comp = len; 2970 len = strlen(diff_buf); 2971 if (len > *max_len_diff) 2972 *max_len_diff = len; 2973 } 2974 break; 2975 case RESFMT_TABLE: { 2976 /* string outputs are left-aligned, number outputs are right-aligned */ 2977 const char *fmt = base_str ? "%s%-*s" : "%s%*s"; 2978 2979 printf(fmt, i == 0 ? "" : COLUMN_SEP, *max_len_base, base_buf); 2980 if (!is_key_stat(id)) { 2981 printf(fmt, COLUMN_SEP, *max_len_comp, comp_buf); 2982 printf(fmt, COLUMN_SEP, *max_len_diff, diff_buf); 2983 } 2984 if (i == env.output_spec.spec_cnt - 1) 2985 printf("\n"); 2986 break; 2987 } 2988 case RESFMT_CSV: 2989 printf("%s%s", i == 0 ? "" : ",", base_buf); 2990 if (!is_key_stat(id)) { 2991 printf("%s%s", i == 0 ? "" : ",", comp_buf); 2992 printf("%s%s", i == 0 ? "" : ",", diff_buf); 2993 } 2994 if (i == env.output_spec.spec_cnt - 1) 2995 printf("\n"); 2996 break; 2997 } 2998 } 2999 3000 if (last && fmt == RESFMT_TABLE) 3001 output_comp_header_underlines(); 3002 } 3003 3004 static int cmp_stats_key(const struct verif_stats *base, const struct verif_stats *comp) 3005 { 3006 int r; 3007 3008 r = strcmp(base->file_name, comp->file_name); 3009 if (r != 0) 3010 return r; 3011 return strcmp(base->prog_name, comp->prog_name); 3012 } 3013 3014 static bool is_join_stat_filter_matched(struct filter *f, const struct verif_stats_join *stats) 3015 { 3016 static const double eps = 1e-9; 3017 const char *str = NULL; 3018 double value = 0.0; 3019 3020 fetch_join_stat_value(stats, f->stat_id, f->stat_var, &str, &value); 3021 3022 if (f->abs) 3023 value = fabs(value); 3024 3025 switch (f->op) { 3026 case OP_EQ: return value > f->value - eps && value < f->value + eps; 3027 case OP_NEQ: return value < f->value - eps || value > f->value + eps; 3028 case OP_LT: return value < f->value - eps; 3029 case OP_LE: return value <= f->value + eps; 3030 case OP_GT: return value > f->value + eps; 3031 case OP_GE: return value >= f->value - eps; 3032 } 3033 3034 fprintf(stderr, "BUG: unknown filter op %d!\n", f->op); 3035 return false; 3036 } 3037 3038 static bool should_output_join_stats(const struct verif_stats_join *stats) 3039 { 3040 struct filter *f; 3041 int i, allow_cnt = 0; 3042 3043 for (i = 0; i < env.deny_filter_cnt; i++) { 3044 f = &env.deny_filters[i]; 3045 if (f->kind != FILTER_STAT) 3046 continue; 3047 3048 if (is_join_stat_filter_matched(f, stats)) 3049 return false; 3050 } 3051 3052 for (i = 0; i < env.allow_filter_cnt; i++) { 3053 f = &env.allow_filters[i]; 3054 if (f->kind != FILTER_STAT) 3055 continue; 3056 allow_cnt++; 3057 3058 if (is_join_stat_filter_matched(f, stats)) 3059 return true; 3060 } 3061 3062 /* if there are no stat allowed filters, pass everything through */ 3063 return allow_cnt == 0; 3064 } 3065 3066 static int handle_comparison_mode(void) 3067 { 3068 struct stat_specs base_specs = {}, comp_specs = {}; 3069 struct stat_specs tmp_sort_spec; 3070 enum resfmt cur_fmt; 3071 int err, i, j, last_idx, cnt; 3072 3073 if (env.filename_cnt != 2) { 3074 fprintf(stderr, "Comparison mode expects exactly two input CSV files!\n\n"); 3075 argp_help(&argp, stderr, ARGP_HELP_USAGE, "veristat"); 3076 return -EINVAL; 3077 } 3078 3079 err = parse_stats_csv(env.filenames[0], &base_specs, 3080 &env.baseline_stats, &env.baseline_stat_cnt); 3081 if (err) { 3082 fprintf(stderr, "Failed to parse stats from '%s': %d\n", env.filenames[0], err); 3083 return err; 3084 } 3085 err = parse_stats_csv(env.filenames[1], &comp_specs, 3086 &env.prog_stats, &env.prog_stat_cnt); 3087 if (err) { 3088 fprintf(stderr, "Failed to parse stats from '%s': %d\n", env.filenames[1], err); 3089 return err; 3090 } 3091 3092 /* To keep it simple we validate that the set and order of stats in 3093 * both CSVs are exactly the same. This can be lifted with a bit more 3094 * pre-processing later. 3095 */ 3096 if (base_specs.spec_cnt != comp_specs.spec_cnt) { 3097 fprintf(stderr, "Number of stats in '%s' and '%s' differs (%d != %d)!\n", 3098 env.filenames[0], env.filenames[1], 3099 base_specs.spec_cnt, comp_specs.spec_cnt); 3100 return -EINVAL; 3101 } 3102 for (i = 0; i < base_specs.spec_cnt; i++) { 3103 if (base_specs.ids[i] != comp_specs.ids[i]) { 3104 fprintf(stderr, "Stats composition differs between '%s' and '%s' (%s != %s)!\n", 3105 env.filenames[0], env.filenames[1], 3106 stat_defs[base_specs.ids[i]].names[0], 3107 stat_defs[comp_specs.ids[i]].names[0]); 3108 return -EINVAL; 3109 } 3110 } 3111 3112 /* Replace user-specified sorting spec with file+prog sorting rule to 3113 * be able to join two datasets correctly. Once we are done, we will 3114 * restore the original sort spec. 3115 */ 3116 tmp_sort_spec = env.sort_spec; 3117 env.sort_spec = join_sort_spec; 3118 qsort(env.prog_stats, env.prog_stat_cnt, sizeof(*env.prog_stats), cmp_prog_stats); 3119 qsort(env.baseline_stats, env.baseline_stat_cnt, sizeof(*env.baseline_stats), cmp_prog_stats); 3120 env.sort_spec = tmp_sort_spec; 3121 3122 /* Join two datasets together. If baseline and comparison datasets 3123 * have different subset of rows (we match by 'object + prog' as 3124 * a unique key) then assume empty/missing/zero value for rows that 3125 * are missing in the opposite data set. 3126 */ 3127 i = j = 0; 3128 while (i < env.baseline_stat_cnt || j < env.prog_stat_cnt) { 3129 const struct verif_stats *base, *comp; 3130 struct verif_stats_join *join; 3131 void *tmp; 3132 int r; 3133 3134 base = i < env.baseline_stat_cnt ? &env.baseline_stats[i] : &fallback_stats; 3135 comp = j < env.prog_stat_cnt ? &env.prog_stats[j] : &fallback_stats; 3136 3137 if (!base->file_name || !base->prog_name) { 3138 fprintf(stderr, "Entry #%d in '%s' doesn't have file and/or program name specified!\n", 3139 i, env.filenames[0]); 3140 return -EINVAL; 3141 } 3142 if (!comp->file_name || !comp->prog_name) { 3143 fprintf(stderr, "Entry #%d in '%s' doesn't have file and/or program name specified!\n", 3144 j, env.filenames[1]); 3145 return -EINVAL; 3146 } 3147 3148 tmp = realloc(env.join_stats, (env.join_stat_cnt + 1) * sizeof(*env.join_stats)); 3149 if (!tmp) 3150 return -ENOMEM; 3151 env.join_stats = tmp; 3152 3153 join = &env.join_stats[env.join_stat_cnt]; 3154 memset(join, 0, sizeof(*join)); 3155 3156 r = cmp_stats_key(base, comp); 3157 if (r == 0) { 3158 join->file_name = base->file_name; 3159 join->prog_name = base->prog_name; 3160 join->stats_a = base; 3161 join->stats_b = comp; 3162 i++; 3163 j++; 3164 } else if (base != &fallback_stats && (comp == &fallback_stats || r < 0)) { 3165 join->file_name = base->file_name; 3166 join->prog_name = base->prog_name; 3167 join->stats_a = base; 3168 join->stats_b = NULL; 3169 i++; 3170 } else if (comp != &fallback_stats && (base == &fallback_stats || r > 0)) { 3171 join->file_name = comp->file_name; 3172 join->prog_name = comp->prog_name; 3173 join->stats_a = NULL; 3174 join->stats_b = comp; 3175 j++; 3176 } else { 3177 fprintf(stderr, "%s:%d: should never reach here i=%i, j=%i", 3178 __FILE__, __LINE__, i, j); 3179 return -EINVAL; 3180 } 3181 env.join_stat_cnt += 1; 3182 } 3183 3184 /* now sort joined results according to sort spec */ 3185 qsort(env.join_stats, env.join_stat_cnt, sizeof(*env.join_stats), cmp_join_stats); 3186 3187 /* for human-readable table output we need to do extra pass to 3188 * calculate column widths, so we substitute current output format 3189 * with RESFMT_TABLE_CALCLEN and later revert it back to RESFMT_TABLE 3190 * and do everything again. 3191 */ 3192 if (env.out_fmt == RESFMT_TABLE) 3193 cur_fmt = RESFMT_TABLE_CALCLEN; 3194 else 3195 cur_fmt = env.out_fmt; 3196 3197 one_more_time: 3198 output_comp_headers(cur_fmt); 3199 3200 last_idx = -1; 3201 cnt = 0; 3202 for (i = 0; i < env.join_stat_cnt; i++) { 3203 const struct verif_stats_join *join = &env.join_stats[i]; 3204 3205 if (!should_output_join_stats(join)) 3206 continue; 3207 3208 if (env.top_n && cnt >= env.top_n) 3209 break; 3210 3211 if (cur_fmt == RESFMT_TABLE_CALCLEN) 3212 last_idx = i; 3213 3214 output_comp_stats(join, cur_fmt, i == last_idx); 3215 3216 cnt++; 3217 } 3218 3219 if (cur_fmt == RESFMT_TABLE_CALCLEN) { 3220 cur_fmt = RESFMT_TABLE; 3221 goto one_more_time; /* ... this time with feeling */ 3222 } 3223 3224 return 0; 3225 } 3226 3227 static bool is_stat_filter_matched(struct filter *f, const struct verif_stats *stats) 3228 { 3229 long value = stats->stats[f->stat_id]; 3230 3231 if (f->abs) 3232 value = value < 0 ? -value : value; 3233 3234 switch (f->op) { 3235 case OP_EQ: return value == f->value; 3236 case OP_NEQ: return value != f->value; 3237 case OP_LT: return value < f->value; 3238 case OP_LE: return value <= f->value; 3239 case OP_GT: return value > f->value; 3240 case OP_GE: return value >= f->value; 3241 } 3242 3243 fprintf(stderr, "BUG: unknown filter op %d!\n", f->op); 3244 return false; 3245 } 3246 3247 static bool should_output_stats(const struct verif_stats *stats) 3248 { 3249 struct filter *f; 3250 int i, allow_cnt = 0; 3251 3252 for (i = 0; i < env.deny_filter_cnt; i++) { 3253 f = &env.deny_filters[i]; 3254 if (f->kind != FILTER_STAT) 3255 continue; 3256 3257 if (is_stat_filter_matched(f, stats)) 3258 return false; 3259 } 3260 3261 for (i = 0; i < env.allow_filter_cnt; i++) { 3262 f = &env.allow_filters[i]; 3263 if (f->kind != FILTER_STAT) 3264 continue; 3265 allow_cnt++; 3266 3267 if (is_stat_filter_matched(f, stats)) 3268 return true; 3269 } 3270 3271 /* if there are no stat allowed filters, pass everything through */ 3272 return allow_cnt == 0; 3273 } 3274 3275 static void output_prog_stats(void) 3276 { 3277 const struct verif_stats *stats; 3278 int i, last_stat_idx = 0, cnt = 0; 3279 3280 if (env.out_fmt == RESFMT_TABLE) { 3281 /* calculate column widths */ 3282 output_headers(RESFMT_TABLE_CALCLEN); 3283 for (i = 0; i < env.prog_stat_cnt; i++) { 3284 stats = &env.prog_stats[i]; 3285 if (!should_output_stats(stats)) 3286 continue; 3287 output_stats(stats, RESFMT_TABLE_CALCLEN, false); 3288 last_stat_idx = i; 3289 } 3290 } 3291 3292 /* actually output the table */ 3293 output_headers(env.out_fmt); 3294 for (i = 0; i < env.prog_stat_cnt; i++) { 3295 stats = &env.prog_stats[i]; 3296 if (!should_output_stats(stats)) 3297 continue; 3298 if (env.top_n && cnt >= env.top_n) 3299 break; 3300 output_stats(stats, env.out_fmt, i == last_stat_idx); 3301 cnt++; 3302 } 3303 } 3304 3305 static int handle_verif_mode(void) 3306 { 3307 int i, err = 0; 3308 3309 if (env.filename_cnt == 0) { 3310 fprintf(stderr, "Please provide path to BPF object file!\n\n"); 3311 argp_help(&argp, stderr, ARGP_HELP_USAGE, "veristat"); 3312 return -EINVAL; 3313 } 3314 3315 create_stat_cgroup(); 3316 for (i = 0; i < env.filename_cnt; i++) { 3317 err = process_obj(env.filenames[i]); 3318 if (err) 3319 fprintf(stderr, "Failed to process '%s': %d\n", env.filenames[i], err); 3320 } 3321 3322 qsort(env.prog_stats, env.prog_stat_cnt, sizeof(*env.prog_stats), cmp_prog_stats); 3323 3324 output_prog_stats(); 3325 3326 destroy_stat_cgroup(); 3327 return err; 3328 } 3329 3330 static int handle_replay_mode(void) 3331 { 3332 struct stat_specs specs = {}; 3333 int err; 3334 3335 if (env.filename_cnt != 1) { 3336 fprintf(stderr, "Replay mode expects exactly one input CSV file!\n\n"); 3337 argp_help(&argp, stderr, ARGP_HELP_USAGE, "veristat"); 3338 return -EINVAL; 3339 } 3340 3341 err = parse_stats_csv(env.filenames[0], &specs, 3342 &env.prog_stats, &env.prog_stat_cnt); 3343 if (err) { 3344 fprintf(stderr, "Failed to parse stats from '%s': %d\n", env.filenames[0], err); 3345 return err; 3346 } 3347 3348 qsort(env.prog_stats, env.prog_stat_cnt, sizeof(*env.prog_stats), cmp_prog_stats); 3349 3350 output_prog_stats(); 3351 3352 return 0; 3353 } 3354 3355 int main(int argc, char **argv) 3356 { 3357 int err = 0, i, j; 3358 3359 if (argp_parse(&argp, argc, argv, 0, NULL, NULL)) 3360 return 1; 3361 3362 if (env.show_version) { 3363 printf("%s\n", argp_program_version); 3364 return 0; 3365 } 3366 3367 if (env.verbose && env.quiet) { 3368 fprintf(stderr, "Verbose and quiet modes are incompatible, please specify just one or neither!\n\n"); 3369 argp_help(&argp, stderr, ARGP_HELP_USAGE, "veristat"); 3370 return 1; 3371 } 3372 if (env.verbose && env.log_level == 0) 3373 env.log_level = 1; 3374 3375 if (env.output_spec.spec_cnt == 0) { 3376 if (env.out_fmt == RESFMT_CSV) 3377 env.output_spec = default_csv_output_spec; 3378 else 3379 env.output_spec = default_output_spec; 3380 } 3381 if (env.sort_spec.spec_cnt == 0) 3382 env.sort_spec = default_sort_spec; 3383 3384 if (env.comparison_mode && env.replay_mode) { 3385 fprintf(stderr, "Can't specify replay and comparison mode at the same time!\n\n"); 3386 argp_help(&argp, stderr, ARGP_HELP_USAGE, "veristat"); 3387 return 1; 3388 } 3389 3390 if (env.comparison_mode) 3391 err = handle_comparison_mode(); 3392 else if (env.replay_mode) 3393 err = handle_replay_mode(); 3394 else 3395 err = handle_verif_mode(); 3396 3397 free_verif_stats(env.prog_stats, env.prog_stat_cnt); 3398 free_verif_stats(env.baseline_stats, env.baseline_stat_cnt); 3399 free(env.join_stats); 3400 for (i = 0; i < env.filename_cnt; i++) 3401 free(env.filenames[i]); 3402 free(env.filenames); 3403 for (i = 0; i < env.allow_filter_cnt; i++) { 3404 free(env.allow_filters[i].any_glob); 3405 free(env.allow_filters[i].file_glob); 3406 free(env.allow_filters[i].prog_glob); 3407 } 3408 free(env.allow_filters); 3409 for (i = 0; i < env.deny_filter_cnt; i++) { 3410 free(env.deny_filters[i].any_glob); 3411 free(env.deny_filters[i].file_glob); 3412 free(env.deny_filters[i].prog_glob); 3413 } 3414 free(env.deny_filters); 3415 for (i = 0; i < env.npresets; ++i) { 3416 free(env.presets[i].full_name); 3417 for (j = 0; j < env.presets[i].atom_count; ++j) { 3418 switch (env.presets[i].atoms[j].type) { 3419 case FIELD_NAME: 3420 free(env.presets[i].atoms[j].name); 3421 break; 3422 case ARRAY_INDEX: 3423 if (env.presets[i].atoms[j].index.type == ENUMERATOR) 3424 free(env.presets[i].atoms[j].index.svalue); 3425 break; 3426 } 3427 } 3428 free(env.presets[i].atoms); 3429 if (env.presets[i].value.type == ENUMERATOR) 3430 free(env.presets[i].value.svalue); 3431 } 3432 free(env.presets); 3433 return -err; 3434 } 3435