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