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

Merge branch 'selftests-bpf-benchmark-all-symbols-for-kprobe-multi'

Menglong Dong says:

====================
selftests/bpf: benchmark all symbols for kprobe-multi

Add the benchmark testcase "kprobe-multi-all", which will hook all the
kernel functions during the testing.

This series is separated out from [1].

Changes since V2:
* add some comment to attach_ksyms_all, which notes that don't run the
testing on a debug kernel

Changes since V1:
* introduce trace_blacklist instead of copy-pasting strcmp in the 2nd
patch
* use fprintf() instead of printf() in 3rd patch

Link: https://lore.kernel.org/bpf/20250817024607.296117-1-dongml2@chinatelecom.cn/ [1]
====================

Link: https://patch.msgid.link/20250904021011.14069-1-dongml2@chinatelecom.cn
Signed-off-by: Alexei Starovoitov <ast@kernel.org>

+319 -219
+4
tools/testing/selftests/bpf/bench.c
··· 512 512 extern const struct bench bench_trig_kprobe_multi; 513 513 extern const struct bench bench_trig_kretprobe_multi; 514 514 extern const struct bench bench_trig_fentry; 515 + extern const struct bench bench_trig_kprobe_multi_all; 516 + extern const struct bench bench_trig_kretprobe_multi_all; 515 517 extern const struct bench bench_trig_fexit; 516 518 extern const struct bench bench_trig_fmodret; 517 519 extern const struct bench bench_trig_tp; ··· 589 587 &bench_trig_kprobe_multi, 590 588 &bench_trig_kretprobe_multi, 591 589 &bench_trig_fentry, 590 + &bench_trig_kprobe_multi_all, 591 + &bench_trig_kretprobe_multi_all, 592 592 &bench_trig_fexit, 593 593 &bench_trig_fmodret, 594 594 &bench_trig_tp,
+61
tools/testing/selftests/bpf/benchs/bench_trigger.c
··· 226 226 attach_bpf(ctx.skel->progs.bench_trigger_fentry); 227 227 } 228 228 229 + static void attach_ksyms_all(struct bpf_program *empty, bool kretprobe) 230 + { 231 + LIBBPF_OPTS(bpf_kprobe_multi_opts, opts); 232 + char **syms = NULL; 233 + size_t cnt = 0; 234 + 235 + /* Some recursive functions will be skipped in 236 + * bpf_get_ksyms -> skip_entry, as they can introduce sufficient 237 + * overhead. However, it's difficut to skip all the recursive 238 + * functions for a debug kernel. 239 + * 240 + * So, don't run the kprobe-multi-all and kretprobe-multi-all on 241 + * a debug kernel. 242 + */ 243 + if (bpf_get_ksyms(&syms, &cnt, true)) { 244 + fprintf(stderr, "failed to get ksyms\n"); 245 + exit(1); 246 + } 247 + 248 + opts.syms = (const char **) syms; 249 + opts.cnt = cnt; 250 + opts.retprobe = kretprobe; 251 + /* attach empty to all the kernel functions except bpf_get_numa_node_id. */ 252 + if (!bpf_program__attach_kprobe_multi_opts(empty, NULL, &opts)) { 253 + fprintf(stderr, "failed to attach bpf_program__attach_kprobe_multi_opts to all\n"); 254 + exit(1); 255 + } 256 + } 257 + 258 + static void trigger_kprobe_multi_all_setup(void) 259 + { 260 + struct bpf_program *prog, *empty; 261 + 262 + setup_ctx(); 263 + empty = ctx.skel->progs.bench_kprobe_multi_empty; 264 + prog = ctx.skel->progs.bench_trigger_kprobe_multi; 265 + bpf_program__set_autoload(empty, true); 266 + bpf_program__set_autoload(prog, true); 267 + load_ctx(); 268 + 269 + attach_ksyms_all(empty, false); 270 + attach_bpf(prog); 271 + } 272 + 273 + static void trigger_kretprobe_multi_all_setup(void) 274 + { 275 + struct bpf_program *prog, *empty; 276 + 277 + setup_ctx(); 278 + empty = ctx.skel->progs.bench_kretprobe_multi_empty; 279 + prog = ctx.skel->progs.bench_trigger_kretprobe_multi; 280 + bpf_program__set_autoload(empty, true); 281 + bpf_program__set_autoload(prog, true); 282 + load_ctx(); 283 + 284 + attach_ksyms_all(empty, true); 285 + attach_bpf(prog); 286 + } 287 + 229 288 static void trigger_fexit_setup(void) 230 289 { 231 290 setup_ctx(); ··· 571 512 BENCH_TRIG_KERNEL(kprobe_multi, "kprobe-multi"); 572 513 BENCH_TRIG_KERNEL(kretprobe_multi, "kretprobe-multi"); 573 514 BENCH_TRIG_KERNEL(fentry, "fentry"); 515 + BENCH_TRIG_KERNEL(kprobe_multi_all, "kprobe-multi-all"); 516 + BENCH_TRIG_KERNEL(kretprobe_multi_all, "kretprobe-multi-all"); 574 517 BENCH_TRIG_KERNEL(fexit, "fexit"); 575 518 BENCH_TRIG_KERNEL(fmodret, "fmodret"); 576 519 BENCH_TRIG_KERNEL(tp, "tp");
+2 -2
tools/testing/selftests/bpf/benchs/run_bench_trigger.sh
··· 6 6 usermode-count kernel-count syscall-count \ 7 7 fentry fexit fmodret \ 8 8 rawtp tp \ 9 - kprobe kprobe-multi \ 10 - kretprobe kretprobe-multi \ 9 + kprobe kprobe-multi kprobe-multi-all \ 10 + kretprobe kretprobe-multi kretprobe-multi-all \ 11 11 ) 12 12 13 13 tests=("$@")
+3 -217
tools/testing/selftests/bpf/prog_tests/kprobe_multi_test.c
··· 422 422 kprobe_multi__destroy(skel); 423 423 } 424 424 425 - static size_t symbol_hash(long key, void *ctx __maybe_unused) 426 - { 427 - return str_hash((const char *) key); 428 - } 429 - 430 - static bool symbol_equal(long key1, long key2, void *ctx __maybe_unused) 431 - { 432 - return strcmp((const char *) key1, (const char *) key2) == 0; 433 - } 434 - 435 - static bool is_invalid_entry(char *buf, bool kernel) 436 - { 437 - if (kernel && strchr(buf, '[')) 438 - return true; 439 - if (!kernel && !strchr(buf, '[')) 440 - return true; 441 - return false; 442 - } 443 - 444 - static bool skip_entry(char *name) 445 - { 446 - /* 447 - * We attach to almost all kernel functions and some of them 448 - * will cause 'suspicious RCU usage' when fprobe is attached 449 - * to them. Filter out the current culprits - arch_cpu_idle 450 - * default_idle and rcu_* functions. 451 - */ 452 - if (!strcmp(name, "arch_cpu_idle")) 453 - return true; 454 - if (!strcmp(name, "default_idle")) 455 - return true; 456 - if (!strncmp(name, "rcu_", 4)) 457 - return true; 458 - if (!strcmp(name, "bpf_dispatcher_xdp_func")) 459 - return true; 460 - if (!strncmp(name, "__ftrace_invalid_address__", 461 - sizeof("__ftrace_invalid_address__") - 1)) 462 - return true; 463 - return false; 464 - } 465 - 466 - /* Do comparison by ignoring '.llvm.<hash>' suffixes. */ 467 - static int compare_name(const char *name1, const char *name2) 468 - { 469 - const char *res1, *res2; 470 - int len1, len2; 471 - 472 - res1 = strstr(name1, ".llvm."); 473 - res2 = strstr(name2, ".llvm."); 474 - len1 = res1 ? res1 - name1 : strlen(name1); 475 - len2 = res2 ? res2 - name2 : strlen(name2); 476 - 477 - if (len1 == len2) 478 - return strncmp(name1, name2, len1); 479 - if (len1 < len2) 480 - return strncmp(name1, name2, len1) <= 0 ? -1 : 1; 481 - return strncmp(name1, name2, len2) >= 0 ? 1 : -1; 482 - } 483 - 484 - static int load_kallsyms_compare(const void *p1, const void *p2) 485 - { 486 - return compare_name(((const struct ksym *)p1)->name, ((const struct ksym *)p2)->name); 487 - } 488 - 489 - static int search_kallsyms_compare(const void *p1, const struct ksym *p2) 490 - { 491 - return compare_name(p1, p2->name); 492 - } 493 - 494 - static int get_syms(char ***symsp, size_t *cntp, bool kernel) 495 - { 496 - size_t cap = 0, cnt = 0; 497 - char *name = NULL, *ksym_name, **syms = NULL; 498 - struct hashmap *map; 499 - struct ksyms *ksyms; 500 - struct ksym *ks; 501 - char buf[256]; 502 - FILE *f; 503 - int err = 0; 504 - 505 - ksyms = load_kallsyms_custom_local(load_kallsyms_compare); 506 - if (!ASSERT_OK_PTR(ksyms, "load_kallsyms_custom_local")) 507 - return -EINVAL; 508 - 509 - /* 510 - * The available_filter_functions contains many duplicates, 511 - * but other than that all symbols are usable in kprobe multi 512 - * interface. 513 - * Filtering out duplicates by using hashmap__add, which won't 514 - * add existing entry. 515 - */ 516 - 517 - if (access("/sys/kernel/tracing/trace", F_OK) == 0) 518 - f = fopen("/sys/kernel/tracing/available_filter_functions", "r"); 519 - else 520 - f = fopen("/sys/kernel/debug/tracing/available_filter_functions", "r"); 521 - 522 - if (!f) 523 - return -EINVAL; 524 - 525 - map = hashmap__new(symbol_hash, symbol_equal, NULL); 526 - if (IS_ERR(map)) { 527 - err = libbpf_get_error(map); 528 - goto error; 529 - } 530 - 531 - while (fgets(buf, sizeof(buf), f)) { 532 - if (is_invalid_entry(buf, kernel)) 533 - continue; 534 - 535 - free(name); 536 - if (sscanf(buf, "%ms$*[^\n]\n", &name) != 1) 537 - continue; 538 - if (skip_entry(name)) 539 - continue; 540 - 541 - ks = search_kallsyms_custom_local(ksyms, name, search_kallsyms_compare); 542 - if (!ks) { 543 - err = -EINVAL; 544 - goto error; 545 - } 546 - 547 - ksym_name = ks->name; 548 - err = hashmap__add(map, ksym_name, 0); 549 - if (err == -EEXIST) { 550 - err = 0; 551 - continue; 552 - } 553 - if (err) 554 - goto error; 555 - 556 - err = libbpf_ensure_mem((void **) &syms, &cap, 557 - sizeof(*syms), cnt + 1); 558 - if (err) 559 - goto error; 560 - 561 - syms[cnt++] = ksym_name; 562 - } 563 - 564 - *symsp = syms; 565 - *cntp = cnt; 566 - 567 - error: 568 - free(name); 569 - fclose(f); 570 - hashmap__free(map); 571 - if (err) 572 - free(syms); 573 - return err; 574 - } 575 - 576 - static int get_addrs(unsigned long **addrsp, size_t *cntp, bool kernel) 577 - { 578 - unsigned long *addr, *addrs, *tmp_addrs; 579 - int err = 0, max_cnt, inc_cnt; 580 - char *name = NULL; 581 - size_t cnt = 0; 582 - char buf[256]; 583 - FILE *f; 584 - 585 - if (access("/sys/kernel/tracing/trace", F_OK) == 0) 586 - f = fopen("/sys/kernel/tracing/available_filter_functions_addrs", "r"); 587 - else 588 - f = fopen("/sys/kernel/debug/tracing/available_filter_functions_addrs", "r"); 589 - 590 - if (!f) 591 - return -ENOENT; 592 - 593 - /* In my local setup, the number of entries is 50k+ so Let us initially 594 - * allocate space to hold 64k entries. If 64k is not enough, incrementally 595 - * increase 1k each time. 596 - */ 597 - max_cnt = 65536; 598 - inc_cnt = 1024; 599 - addrs = malloc(max_cnt * sizeof(long)); 600 - if (addrs == NULL) { 601 - err = -ENOMEM; 602 - goto error; 603 - } 604 - 605 - while (fgets(buf, sizeof(buf), f)) { 606 - if (is_invalid_entry(buf, kernel)) 607 - continue; 608 - 609 - free(name); 610 - if (sscanf(buf, "%p %ms$*[^\n]\n", &addr, &name) != 2) 611 - continue; 612 - if (skip_entry(name)) 613 - continue; 614 - 615 - if (cnt == max_cnt) { 616 - max_cnt += inc_cnt; 617 - tmp_addrs = realloc(addrs, max_cnt); 618 - if (!tmp_addrs) { 619 - err = -ENOMEM; 620 - goto error; 621 - } 622 - addrs = tmp_addrs; 623 - } 624 - 625 - addrs[cnt++] = (unsigned long)addr; 626 - } 627 - 628 - *addrsp = addrs; 629 - *cntp = cnt; 630 - 631 - error: 632 - free(name); 633 - fclose(f); 634 - if (err) 635 - free(addrs); 636 - return err; 637 - } 638 - 639 425 static void do_bench_test(struct kprobe_multi_empty *skel, struct bpf_kprobe_multi_opts *opts) 640 426 { 641 427 long attach_start_ns, attach_end_ns; ··· 456 670 char **syms = NULL; 457 671 size_t cnt = 0; 458 672 459 - if (!ASSERT_OK(get_syms(&syms, &cnt, kernel), "get_syms")) 673 + if (!ASSERT_OK(bpf_get_ksyms(&syms, &cnt, kernel), "bpf_get_ksyms")) 460 674 return; 461 675 462 676 skel = kprobe_multi_empty__open_and_load(); ··· 482 696 size_t cnt = 0; 483 697 int err; 484 698 485 - err = get_addrs(&addrs, &cnt, kernel); 699 + err = bpf_get_addrs(&addrs, &cnt, kernel); 486 700 if (err == -ENOENT) { 487 701 test__skip(); 488 702 return; 489 703 } 490 704 491 - if (!ASSERT_OK(err, "get_addrs")) 705 + if (!ASSERT_OK(err, "bpf_get_addrs")) 492 706 return; 493 707 494 708 skel = kprobe_multi_empty__open_and_load();
+12
tools/testing/selftests/bpf/progs/trigger_bench.c
··· 97 97 return 0; 98 98 } 99 99 100 + SEC("?kprobe.multi/bpf_get_numa_node_id") 101 + int bench_kprobe_multi_empty(void *ctx) 102 + { 103 + return 0; 104 + } 105 + 100 106 SEC("?kretprobe.multi/bpf_get_numa_node_id") 101 107 int bench_trigger_kretprobe_multi(void *ctx) 102 108 { 103 109 inc_counter(); 110 + return 0; 111 + } 112 + 113 + SEC("?kretprobe.multi/bpf_get_numa_node_id") 114 + int bench_kretprobe_multi_empty(void *ctx) 115 + { 104 116 return 0; 105 117 } 106 118
+234
tools/testing/selftests/bpf/trace_helpers.c
··· 17 17 #include <linux/limits.h> 18 18 #include <libelf.h> 19 19 #include <gelf.h> 20 + #include "bpf/hashmap.h" 20 21 #include "bpf/libbpf_internal.h" 22 + #include "bpf_util.h" 21 23 22 24 #define TRACEFS_PIPE "/sys/kernel/tracing/trace_pipe" 23 25 #define DEBUGFS_PIPE "/sys/kernel/debug/tracing/trace_pipe" ··· 520 518 void read_trace_pipe(void) 521 519 { 522 520 read_trace_pipe_iter(trace_pipe_cb, NULL, 0); 521 + } 522 + 523 + static size_t symbol_hash(long key, void *ctx __maybe_unused) 524 + { 525 + return str_hash((const char *) key); 526 + } 527 + 528 + static bool symbol_equal(long key1, long key2, void *ctx __maybe_unused) 529 + { 530 + return strcmp((const char *) key1, (const char *) key2) == 0; 531 + } 532 + 533 + static bool is_invalid_entry(char *buf, bool kernel) 534 + { 535 + if (kernel && strchr(buf, '[')) 536 + return true; 537 + if (!kernel && !strchr(buf, '[')) 538 + return true; 539 + return false; 540 + } 541 + 542 + static const char * const trace_blacklist[] = { 543 + "migrate_disable", 544 + "migrate_enable", 545 + "rcu_read_unlock_strict", 546 + "preempt_count_add", 547 + "preempt_count_sub", 548 + "__rcu_read_lock", 549 + "__rcu_read_unlock", 550 + "bpf_get_numa_node_id", 551 + }; 552 + 553 + static bool skip_entry(char *name) 554 + { 555 + int i; 556 + 557 + /* 558 + * We attach to almost all kernel functions and some of them 559 + * will cause 'suspicious RCU usage' when fprobe is attached 560 + * to them. Filter out the current culprits - arch_cpu_idle 561 + * default_idle and rcu_* functions. 562 + */ 563 + if (!strcmp(name, "arch_cpu_idle")) 564 + return true; 565 + if (!strcmp(name, "default_idle")) 566 + return true; 567 + if (!strncmp(name, "rcu_", 4)) 568 + return true; 569 + if (!strcmp(name, "bpf_dispatcher_xdp_func")) 570 + return true; 571 + if (!strncmp(name, "__ftrace_invalid_address__", 572 + sizeof("__ftrace_invalid_address__") - 1)) 573 + return true; 574 + 575 + for (i = 0; i < ARRAY_SIZE(trace_blacklist); i++) { 576 + if (!strcmp(name, trace_blacklist[i])) 577 + return true; 578 + } 579 + 580 + return false; 581 + } 582 + 583 + /* Do comparison by ignoring '.llvm.<hash>' suffixes. */ 584 + static int compare_name(const char *name1, const char *name2) 585 + { 586 + const char *res1, *res2; 587 + int len1, len2; 588 + 589 + res1 = strstr(name1, ".llvm."); 590 + res2 = strstr(name2, ".llvm."); 591 + len1 = res1 ? res1 - name1 : strlen(name1); 592 + len2 = res2 ? res2 - name2 : strlen(name2); 593 + 594 + if (len1 == len2) 595 + return strncmp(name1, name2, len1); 596 + if (len1 < len2) 597 + return strncmp(name1, name2, len1) <= 0 ? -1 : 1; 598 + return strncmp(name1, name2, len2) >= 0 ? 1 : -1; 599 + } 600 + 601 + static int load_kallsyms_compare(const void *p1, const void *p2) 602 + { 603 + return compare_name(((const struct ksym *)p1)->name, ((const struct ksym *)p2)->name); 604 + } 605 + 606 + static int search_kallsyms_compare(const void *p1, const struct ksym *p2) 607 + { 608 + return compare_name(p1, p2->name); 609 + } 610 + 611 + int bpf_get_ksyms(char ***symsp, size_t *cntp, bool kernel) 612 + { 613 + size_t cap = 0, cnt = 0; 614 + char *name = NULL, *ksym_name, **syms = NULL; 615 + struct hashmap *map; 616 + struct ksyms *ksyms; 617 + struct ksym *ks; 618 + char buf[256]; 619 + FILE *f; 620 + int err = 0; 621 + 622 + ksyms = load_kallsyms_custom_local(load_kallsyms_compare); 623 + if (!ksyms) 624 + return -EINVAL; 625 + 626 + /* 627 + * The available_filter_functions contains many duplicates, 628 + * but other than that all symbols are usable to trace. 629 + * Filtering out duplicates by using hashmap__add, which won't 630 + * add existing entry. 631 + */ 632 + 633 + if (access("/sys/kernel/tracing/trace", F_OK) == 0) 634 + f = fopen("/sys/kernel/tracing/available_filter_functions", "r"); 635 + else 636 + f = fopen("/sys/kernel/debug/tracing/available_filter_functions", "r"); 637 + 638 + if (!f) 639 + return -EINVAL; 640 + 641 + map = hashmap__new(symbol_hash, symbol_equal, NULL); 642 + if (IS_ERR(map)) { 643 + err = libbpf_get_error(map); 644 + goto error; 645 + } 646 + 647 + while (fgets(buf, sizeof(buf), f)) { 648 + if (is_invalid_entry(buf, kernel)) 649 + continue; 650 + 651 + free(name); 652 + if (sscanf(buf, "%ms$*[^\n]\n", &name) != 1) 653 + continue; 654 + if (skip_entry(name)) 655 + continue; 656 + 657 + ks = search_kallsyms_custom_local(ksyms, name, search_kallsyms_compare); 658 + if (!ks) { 659 + err = -EINVAL; 660 + goto error; 661 + } 662 + 663 + ksym_name = ks->name; 664 + err = hashmap__add(map, ksym_name, 0); 665 + if (err == -EEXIST) { 666 + err = 0; 667 + continue; 668 + } 669 + if (err) 670 + goto error; 671 + 672 + err = libbpf_ensure_mem((void **) &syms, &cap, 673 + sizeof(*syms), cnt + 1); 674 + if (err) 675 + goto error; 676 + 677 + syms[cnt++] = ksym_name; 678 + } 679 + 680 + *symsp = syms; 681 + *cntp = cnt; 682 + 683 + error: 684 + free(name); 685 + fclose(f); 686 + hashmap__free(map); 687 + if (err) 688 + free(syms); 689 + return err; 690 + } 691 + 692 + int bpf_get_addrs(unsigned long **addrsp, size_t *cntp, bool kernel) 693 + { 694 + unsigned long *addr, *addrs, *tmp_addrs; 695 + int err = 0, max_cnt, inc_cnt; 696 + char *name = NULL; 697 + size_t cnt = 0; 698 + char buf[256]; 699 + FILE *f; 700 + 701 + if (access("/sys/kernel/tracing/trace", F_OK) == 0) 702 + f = fopen("/sys/kernel/tracing/available_filter_functions_addrs", "r"); 703 + else 704 + f = fopen("/sys/kernel/debug/tracing/available_filter_functions_addrs", "r"); 705 + 706 + if (!f) 707 + return -ENOENT; 708 + 709 + /* In my local setup, the number of entries is 50k+ so Let us initially 710 + * allocate space to hold 64k entries. If 64k is not enough, incrementally 711 + * increase 1k each time. 712 + */ 713 + max_cnt = 65536; 714 + inc_cnt = 1024; 715 + addrs = malloc(max_cnt * sizeof(long)); 716 + if (addrs == NULL) { 717 + err = -ENOMEM; 718 + goto error; 719 + } 720 + 721 + while (fgets(buf, sizeof(buf), f)) { 722 + if (is_invalid_entry(buf, kernel)) 723 + continue; 724 + 725 + free(name); 726 + if (sscanf(buf, "%p %ms$*[^\n]\n", &addr, &name) != 2) 727 + continue; 728 + if (skip_entry(name)) 729 + continue; 730 + 731 + if (cnt == max_cnt) { 732 + max_cnt += inc_cnt; 733 + tmp_addrs = realloc(addrs, max_cnt); 734 + if (!tmp_addrs) { 735 + err = -ENOMEM; 736 + goto error; 737 + } 738 + addrs = tmp_addrs; 739 + } 740 + 741 + addrs[cnt++] = (unsigned long)addr; 742 + } 743 + 744 + *addrsp = addrs; 745 + *cntp = cnt; 746 + 747 + error: 748 + free(name); 749 + fclose(f); 750 + if (err) 751 + free(addrs); 752 + return err; 523 753 }
+3
tools/testing/selftests/bpf/trace_helpers.h
··· 41 41 42 42 int read_build_id(const char *path, char *build_id, size_t size); 43 43 44 + int bpf_get_ksyms(char ***symsp, size_t *cntp, bool kernel); 45 + int bpf_get_addrs(unsigned long **addrsp, size_t *cntp, bool kernel); 46 + 44 47 #endif