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