diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c index ddaa41a70676c..f9d5495069d4d 100644 --- a/kernel/bpf/helpers.c +++ b/kernel/bpf/helpers.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only /* Copyright (c) 2011-2014 PLUMgrid, http://plumgrid.com */ +#include "linux/uaccess.h" #include #include #include @@ -3195,6 +3196,291 @@ __bpf_kfunc void bpf_local_irq_restore(unsigned long *flags__irq_flag) local_irq_restore(*flags__irq_flag); } +/* Kfuncs for string operations. + * + * Since strings are not necessarily %NUL-terminated, we cannot directly call + * in-kernel implementations. Instead, unbounded variants are open-coded with + * using __get_kernel_nofault instead of plain dereference to make them safe. + * Bounded variants use params with the __sz suffix so safety is assured by the + * verifier and we can use the in-kernel (potentially optimized) functions. + */ + +/** + * bpf_strcmp - Compare two strings + * @cs: One string + * @ct: Another string + */ +__bpf_kfunc int bpf_strcmp(const char *cs, const char *ct) +{ + int i = 0, ret = 0; + char c1, c2; + + pagefault_disable(); + while (i++ < XATTR_SIZE_MAX) { + __get_kernel_nofault(&c1, cs++, char, cs_out); + __get_kernel_nofault(&c2, ct++, char, ct_out); + if (c1 != c2) { + ret = c1 < c2 ? -1 : 1; + goto out; + } + if (!c1) + goto out; + } +cs_out: + ret = -1; + goto out; +ct_out: + ret = 1; +out: + pagefault_enable(); + return ret; +} + +/** + * bpf_strchr - Find the first occurrence of a character in a string + * @s: The string to be searched + * @c: The character to search for + * + * Note that the %NUL-terminator is considered part of the string, and can + * be searched for. + */ +__bpf_kfunc char *bpf_strchr(const char *s, int c) +{ + char *ret = NULL; + int i = 0; + char sc; + + pagefault_disable(); + while (i++ < XATTR_SIZE_MAX) { + __get_kernel_nofault(&sc, s, char, out); + if (sc == (char)c) { + ret = (char *)s; + break; + } + if (sc == '\0') + break; + s++; + } +out: + pagefault_enable(); + return ret; +} + +/** + * bpf_strchrnul - Find and return a character in a string, or end of string + * @s: The string to be searched + * @c: The character to search for + * + * Returns pointer to first occurrence of 'c' in s. If c is not found, then + * return a pointer to the null byte at the end of s. + */ +__bpf_kfunc char *bpf_strchrnul(const char *s, int c) +{ + char *ret = NULL; + int i = 0; + char sc; + + pagefault_disable(); + while (i++ < XATTR_SIZE_MAX) { + __get_kernel_nofault(&sc, s, char, out); + if (sc == '\0' || sc == (char)c) { + ret = (char *)s; + break; + } + s++; + } +out: + pagefault_enable(); + return ret; +} + +/** + * bpf_strnchr - Find a character in a length limited string + * @s: The string to be searched + * @s__sz: The number of characters to be searched + * @c: The character to search for + * + * Note that the %NUL-terminator is considered part of the string, and can + * be searched for. + */ +__bpf_kfunc char *bpf_strnchr(void *s, u32 s__sz, int c) +{ + return strnchr(s, s__sz, c); +} + +/** + * bpf_strnchrnul - Find and return a character in a length limited string, + * or end of string + * @s: The string to be searched + * @s__sz: The number of characters to be searched + * @c: The character to search for + * + * Returns pointer to the first occurrence of 'c' in s. If c is not found, + * then return a pointer to the last character of the string. + */ +__bpf_kfunc char *bpf_strnchrnul(void *s, u32 s__sz, int c) +{ + return strnchrnul(s, s__sz, c); +} + +/** + * bpf_strrchr - Find the last occurrence of a character in a string + * @s: The string to be searched + * @c: The character to search for + */ +__bpf_kfunc char *bpf_strrchr(const char *s, int c) +{ + char *ret = NULL; + int i = 0; + char sc; + + pagefault_disable(); + while (i++ < XATTR_SIZE_MAX) { + __get_kernel_nofault(&sc, s, char, out); + if (sc == '\0') + break; + if (sc == (char)c) + ret = (char *)s; + s++; + } +out: + pagefault_enable(); + return (char *)ret; +} + +__bpf_kfunc size_t bpf_strlen(const char *s) +{ + int i = 0; + char c; + + pagefault_disable(); + while (i < XATTR_SIZE_MAX) { + __get_kernel_nofault(&c, s++, char, out); + if (c == '\0') + break; + i++; + } +out: + pagefault_enable(); + return i; +} + +__bpf_kfunc size_t bpf_strnlen(void *s, u32 s__sz) +{ + return strnlen(s, s__sz); +} + +/** + * bpf_strspn - Calculate the length of the initial substring of @s which only contain letters in @accept + * @s: The string to be searched + * @accept: The string to search for + */ +__bpf_kfunc size_t bpf_strspn(const char *s, const char *accept) +{ + int i = 0; + char c; + + pagefault_disable(); + while (i < XATTR_SIZE_MAX) { + __get_kernel_nofault(&c, s++, char, out); + if (c == '\0' || !bpf_strchr(accept, c)) + break; + i++; + } +out: + pagefault_enable(); + return i; +} + +/** + * strcspn - Calculate the length of the initial substring of @s which does not contain letters in @reject + * @s: The string to be searched + * @reject: The string to avoid + */ +__bpf_kfunc size_t bpf_strcspn(const char *s, const char *reject) +{ + int i = 0; + char c; + + pagefault_disable(); + while (i < XATTR_SIZE_MAX) { + __get_kernel_nofault(&c, s++, char, out); + if (c == '\0' || bpf_strchr(reject, c)) + break; + i++; + } +out: + pagefault_enable(); + return i; +} + +/** + * bpf_strpbrk - Find the first occurrence of a set of characters + * @cs: The string to be searched + * @ct: The characters to search for + */ +__bpf_kfunc char *bpf_strpbrk(const char *cs, const char *ct) +{ + char *ret = NULL; + int i = 0; + char c; + + pagefault_disable(); + while (i++ < XATTR_SIZE_MAX) { + __get_kernel_nofault(&c, cs, char, out); + if (c == '\0') + break; + if (bpf_strchr(ct, c)) { + ret = (char *)cs; + break; + } + cs++; + } +out: + pagefault_enable(); + return ret; +} + +/** + * bpf_strstr - Find the first substring in a %NUL terminated string + * @s1: The string to be searched + * @s2: The string to search for + */ +__bpf_kfunc char *bpf_strstr(const char *s1, const char *s2) +{ + size_t l1, l2; + + l2 = bpf_strlen(s2); + if (!l2) + return (char *)s1; + l1 = bpf_strlen(s1); + while (l1 >= l2) { + l1--; + if (!memcmp(s1, s2, l2)) + return (char *)s1; + s1++; + } + return NULL; +} + +/** + * bpf_strnstr - Find the first substring in a length-limited string + * @s1: The string to be searched + * @s1__sz: The size of @s1 + * @s2: The string to search for + * @s2__sz: The size of @s2 + */ +__bpf_kfunc char *bpf_strnstr(void *s1, u32 s1__sz, void *s2, u32 s2__sz) +{ + /* strnstr() uses strlen() to get the length of s2. Since this is not + * safe in BPF context for non-%NUL-terminated strings, use strnlen + * first to make it safe. + */ + if (strnlen(s2, s2__sz) == s2__sz) + return NULL; + return strnstr(s1, s2, s1__sz); +} + __bpf_kfunc_end_defs(); BTF_KFUNCS_START(generic_btf_ids) @@ -3295,6 +3581,19 @@ BTF_ID_FLAGS(func, bpf_iter_kmem_cache_next, KF_ITER_NEXT | KF_RET_NULL | KF_SLE BTF_ID_FLAGS(func, bpf_iter_kmem_cache_destroy, KF_ITER_DESTROY | KF_SLEEPABLE) BTF_ID_FLAGS(func, bpf_local_irq_save) BTF_ID_FLAGS(func, bpf_local_irq_restore) +BTF_ID_FLAGS(func, bpf_strcmp); +BTF_ID_FLAGS(func, bpf_strchr); +BTF_ID_FLAGS(func, bpf_strchrnul); +BTF_ID_FLAGS(func, bpf_strnchr); +BTF_ID_FLAGS(func, bpf_strnchrnul); +BTF_ID_FLAGS(func, bpf_strrchr); +BTF_ID_FLAGS(func, bpf_strlen); +BTF_ID_FLAGS(func, bpf_strnlen); +BTF_ID_FLAGS(func, bpf_strspn); +BTF_ID_FLAGS(func, bpf_strcspn); +BTF_ID_FLAGS(func, bpf_strpbrk); +BTF_ID_FLAGS(func, bpf_strstr); +BTF_ID_FLAGS(func, bpf_strnstr); BTF_KFUNCS_END(common_btf_ids) static const struct btf_kfunc_id_set common_kfunc_set = { diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile index 66bb50356be08..1ef173bf8ceab 100644 --- a/tools/testing/selftests/bpf/Makefile +++ b/tools/testing/selftests/bpf/Makefile @@ -811,6 +811,7 @@ $(OUTPUT)/bench_local_storage_create.o: $(OUTPUT)/bench_local_storage_create.ske $(OUTPUT)/bench_bpf_hashmap_lookup.o: $(OUTPUT)/bpf_hashmap_lookup.skel.h $(OUTPUT)/bench_htab_mem.o: $(OUTPUT)/htab_mem_bench.skel.h $(OUTPUT)/bench_bpf_crypto.o: $(OUTPUT)/crypto_bench.skel.h +$(OUTPUT)/bench_string_kfuncs.o: $(OUTPUT)/string_kfuncs_bench.skel.h $(OUTPUT)/bench.o: bench.h testing_helpers.h $(BPFOBJ) $(OUTPUT)/bench: LDLIBS += -lm $(OUTPUT)/bench: $(OUTPUT)/bench.o \ @@ -831,6 +832,7 @@ $(OUTPUT)/bench: $(OUTPUT)/bench.o \ $(OUTPUT)/bench_local_storage_create.o \ $(OUTPUT)/bench_htab_mem.o \ $(OUTPUT)/bench_bpf_crypto.o \ + $(OUTPUT)/bench_string_kfuncs.o \ # $(call msg,BINARY,,$@) $(Q)$(CC) $(CFLAGS) $(LDFLAGS) $(filter %.a %.o,$^) $(LDLIBS) -o $@ diff --git a/tools/testing/selftests/bpf/bench.c b/tools/testing/selftests/bpf/bench.c index 1bd403a5ef7b3..5aa7f63436f63 100644 --- a/tools/testing/selftests/bpf/bench.c +++ b/tools/testing/selftests/bpf/bench.c @@ -283,6 +283,7 @@ extern struct argp bench_local_storage_create_argp; extern struct argp bench_htab_mem_argp; extern struct argp bench_trigger_batch_argp; extern struct argp bench_crypto_argp; +extern struct argp bench_string_kfuncs_argp; static const struct argp_child bench_parsers[] = { { &bench_ringbufs_argp, 0, "Ring buffers benchmark", 0 }, @@ -297,6 +298,7 @@ static const struct argp_child bench_parsers[] = { { &bench_htab_mem_argp, 0, "hash map memory benchmark", 0 }, { &bench_trigger_batch_argp, 0, "BPF triggering benchmark", 0 }, { &bench_crypto_argp, 0, "bpf crypto benchmark", 0 }, + { &bench_string_kfuncs_argp, 0, "string kfuncs benchmark", 0 }, {}, }; @@ -550,6 +552,16 @@ extern const struct bench bench_htab_mem; extern const struct bench bench_crypto_encrypt; extern const struct bench bench_crypto_decrypt; +/* string kfunc benchmarks */ +extern const struct bench bench_string_kfuncs_strlen; +extern const struct bench bench_string_kfuncs_strnlen; +extern const struct bench bench_string_kfuncs_strchr; +extern const struct bench bench_string_kfuncs_strnchr; +extern const struct bench bench_string_kfuncs_strchrnul; +extern const struct bench bench_string_kfuncs_strnchrnul; +extern const struct bench bench_string_kfuncs_strstr; +extern const struct bench bench_string_kfuncs_strnstr; + static const struct bench *benchs[] = { &bench_count_global, &bench_count_local, @@ -609,6 +621,15 @@ static const struct bench *benchs[] = { &bench_htab_mem, &bench_crypto_encrypt, &bench_crypto_decrypt, + /* string kfuncs */ + &bench_string_kfuncs_strlen, + &bench_string_kfuncs_strnlen, + &bench_string_kfuncs_strchr, + &bench_string_kfuncs_strnchr, + &bench_string_kfuncs_strchrnul, + &bench_string_kfuncs_strnchrnul, + &bench_string_kfuncs_strstr, + &bench_string_kfuncs_strnstr, }; static void find_benchmark(void) diff --git a/tools/testing/selftests/bpf/benchs/bench_string_kfuncs.c b/tools/testing/selftests/bpf/benchs/bench_string_kfuncs.c new file mode 100644 index 0000000000000..a2e11af092ce9 --- /dev/null +++ b/tools/testing/selftests/bpf/benchs/bench_string_kfuncs.c @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2025. Red Hat, Inc. */ +#include +#include "bench.h" +#include "string_kfuncs_bench.skel.h" + +static struct string_kfuncs_ctx { + struct string_kfuncs_bench *skel; +} ctx; + +static struct string_kfuncs_args { + u32 str_len; +} args = { + .str_len = 32, +}; + +enum { + ARG_STR_LEN = 5000, +}; + +static const struct argp_option opts[] = { + { "str-len", ARG_STR_LEN, "STR_LEN", 0, "Set the length of string(s)" }, + {}, +}; + +static error_t string_kfuncs_parse_arg(int key, char *arg, struct argp_state *state) +{ + switch (key) { + case ARG_STR_LEN: + args.str_len = strtoul(arg, NULL, 10); + if (!args.str_len || + args.str_len >= sizeof(ctx.skel->bss->str)) { + fprintf(stderr, "Invalid str len (limit %zu)\n", + sizeof(ctx.skel->bss->str) - 1); + argp_usage(state); + } + break; + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; +} + +const struct argp bench_string_kfuncs_argp = { + .options = opts, + .parser = string_kfuncs_parse_arg, +}; + +static void string_kfuncs_validate(void) +{ + if (env.consumer_cnt != 0) { + fprintf(stderr, "string_kfuncs benchmark doesn't support consumer!\n"); + exit(1); + } +} + +static void string_kfuncs_setup(void) +{ + int err; + char *str; + size_t i, sz, quarter; + + sz = sizeof(ctx.skel->bss->str); + if (!sz) { + fprintf(stderr, "invalid string size (%zu)\n", sz); + exit(1); + } + + setup_libbpf(); + + ctx.skel = string_kfuncs_bench__open(); + if (!ctx.skel) { + fprintf(stderr, "failed to open skeleton\n"); + exit(1); + } + + /* Fill str with random digits 1-9 */ + srandom(time(NULL)); + str = ctx.skel->bss->str; + for (i = 0; i < args.str_len - 1; i++) + str[i] = '1' + random() % 9; + + /* For strchr and variants - set the last character to '0' */ + str[args.str_len - 1] = '0'; + str[args.str_len] = '\0'; + + /* For strstr and variants - copy the last quarter of str to substr */ + quarter = args.str_len / 4; + memcpy(ctx.skel->bss->substr, str + args.str_len - quarter, quarter + 1); + + ctx.skel->rodata->str_len = args.str_len; + + err = string_kfuncs_bench__load(ctx.skel); + if (err) { + fprintf(stderr, "failed to load skeleton\n"); + string_kfuncs_bench__destroy(ctx.skel); + exit(1); + } +} + +static void string_kfuncs_attach_prog(struct bpf_program *prog) +{ + struct bpf_link *link; + + link = bpf_program__attach(prog); + if (!link) { + fprintf(stderr, "failed to attach program!\n"); + exit(1); + } +} + +static void string_kfuncs_strlen_setup(void) +{ + string_kfuncs_setup(); + string_kfuncs_attach_prog(ctx.skel->progs.strlen_bench); +} + +static void string_kfuncs_strnlen_setup(void) +{ + string_kfuncs_setup(); + string_kfuncs_attach_prog(ctx.skel->progs.strnlen_bench); +} + +static void string_kfuncs_strchr_setup(void) +{ + string_kfuncs_setup(); + string_kfuncs_attach_prog(ctx.skel->progs.strchr_bench); +} + +static void string_kfuncs_strnchr_setup(void) +{ + string_kfuncs_setup(); + string_kfuncs_attach_prog(ctx.skel->progs.strnchr_bench); +} + +static void string_kfuncs_strchrnul_setup(void) +{ + string_kfuncs_setup(); + string_kfuncs_attach_prog(ctx.skel->progs.strchrnul_bench); +} + +static void string_kfuncs_strnchrnul_setup(void) +{ + string_kfuncs_setup(); + string_kfuncs_attach_prog(ctx.skel->progs.strnchrnul_bench); +} + +static void string_kfuncs_strstr_setup(void) +{ + string_kfuncs_setup(); + string_kfuncs_attach_prog(ctx.skel->progs.strstr_bench); +} + +static void string_kfuncs_strnstr_setup(void) +{ + string_kfuncs_setup(); + string_kfuncs_attach_prog(ctx.skel->progs.strnstr_bench); +} + +static void *string_kfuncs_producer(void *ctx) +{ + while (true) + (void)syscall(__NR_getpgid); + return NULL; +} + +static void string_kfuncs_measure(struct bench_res *res) +{ + res->hits = atomic_swap(&ctx.skel->bss->hits, 0); +} + +const struct bench bench_string_kfuncs_strlen = { + .name = "string-kfuncs-strlen", + .argp = &bench_string_kfuncs_argp, + .validate = string_kfuncs_validate, + .setup = string_kfuncs_strlen_setup, + .producer_thread = string_kfuncs_producer, + .measure = string_kfuncs_measure, + .report_progress = hits_drops_report_progress, + .report_final = hits_drops_report_final, +}; + +const struct bench bench_string_kfuncs_strnlen = { + .name = "string-kfuncs-strnlen", + .argp = &bench_string_kfuncs_argp, + .validate = string_kfuncs_validate, + .setup = string_kfuncs_strnlen_setup, + .producer_thread = string_kfuncs_producer, + .measure = string_kfuncs_measure, + .report_progress = hits_drops_report_progress, + .report_final = hits_drops_report_final, +}; + +const struct bench bench_string_kfuncs_strchr = { + .name = "string-kfuncs-strchr", + .argp = &bench_string_kfuncs_argp, + .validate = string_kfuncs_validate, + .setup = string_kfuncs_strchr_setup, + .producer_thread = string_kfuncs_producer, + .measure = string_kfuncs_measure, + .report_progress = hits_drops_report_progress, + .report_final = hits_drops_report_final, +}; + +const struct bench bench_string_kfuncs_strnchr = { + .name = "string-kfuncs-strnchr", + .argp = &bench_string_kfuncs_argp, + .validate = string_kfuncs_validate, + .setup = string_kfuncs_strnchr_setup, + .producer_thread = string_kfuncs_producer, + .measure = string_kfuncs_measure, + .report_progress = hits_drops_report_progress, + .report_final = hits_drops_report_final, +}; + +const struct bench bench_string_kfuncs_strchrnul = { + .name = "string-kfuncs-strchrnul", + .argp = &bench_string_kfuncs_argp, + .validate = string_kfuncs_validate, + .setup = string_kfuncs_strchrnul_setup, + .producer_thread = string_kfuncs_producer, + .measure = string_kfuncs_measure, + .report_progress = hits_drops_report_progress, + .report_final = hits_drops_report_final, +}; + +const struct bench bench_string_kfuncs_strnchrnul = { + .name = "string-kfuncs-strnchrnul", + .argp = &bench_string_kfuncs_argp, + .validate = string_kfuncs_validate, + .setup = string_kfuncs_strnchrnul_setup, + .producer_thread = string_kfuncs_producer, + .measure = string_kfuncs_measure, + .report_progress = hits_drops_report_progress, + .report_final = hits_drops_report_final, +}; + +const struct bench bench_string_kfuncs_strstr = { + .name = "string-kfuncs-strstr", + .argp = &bench_string_kfuncs_argp, + .validate = string_kfuncs_validate, + .setup = string_kfuncs_strstr_setup, + .producer_thread = string_kfuncs_producer, + .measure = string_kfuncs_measure, + .report_progress = hits_drops_report_progress, + .report_final = hits_drops_report_final, +}; + +const struct bench bench_string_kfuncs_strnstr = { + .name = "string-kfuncs-strnstr", + .argp = &bench_string_kfuncs_argp, + .validate = string_kfuncs_validate, + .setup = string_kfuncs_strnstr_setup, + .producer_thread = string_kfuncs_producer, + .measure = string_kfuncs_measure, + .report_progress = hits_drops_report_progress, + .report_final = hits_drops_report_final, +}; diff --git a/tools/testing/selftests/bpf/benchs/run_bench_string_kfuncs.sh b/tools/testing/selftests/bpf/benchs/run_bench_string_kfuncs.sh new file mode 100755 index 0000000000000..5e635681cd85f --- /dev/null +++ b/tools/testing/selftests/bpf/benchs/run_bench_string_kfuncs.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 + +source ./benchs/run_common.sh + +set -eufo pipefail + +header "strlen/strnlen" +for s in 1 8 64 512 2048 4095; do + for b in strlen strnlen; do + summarize ${b}-${s} "$($RUN_BENCH --str-len=$s string-kfuncs-${b})" + done +done + +header "strchr/strnchr" +for s in 1 8 64 512 2048 4095; do + for b in strchr strnchr; do + summarize ${b}-${s} "$($RUN_BENCH --str-len=$s string-kfuncs-${b})" + done +done + +header "strchrnul/strnchrnul" +for s in 1 8 64 512 2048 4095; do + for b in strchrnul strnchrnul; do + summarize ${b}-${s} "$($RUN_BENCH --str-len=$s string-kfuncs-${b})" + done +done + +header "strstr/strnstr" +for s in 8 64 512 2048 4095; do + for b in strstr strnstr; do + summarize ${b}-${s} "$($RUN_BENCH --str-len=$s string-kfuncs-${b})" + done +done diff --git a/tools/testing/selftests/bpf/prog_tests/string_kfuncs.c b/tools/testing/selftests/bpf/prog_tests/string_kfuncs.c new file mode 100644 index 0000000000000..79dab172eb925 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/string_kfuncs.c @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2025 Red Hat, Inc.*/ +#include +#include "string_kfuncs.skel.h" + +void test_string_kfuncs(void) +{ + RUN_TESTS(string_kfuncs); +} + diff --git a/tools/testing/selftests/bpf/progs/string_kfuncs.c b/tools/testing/selftests/bpf/progs/string_kfuncs.c new file mode 100644 index 0000000000000..9fb1ed5ba1fa7 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/string_kfuncs.c @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2025 Red Hat, Inc.*/ +#include "vmlinux.h" +#include +#include "bpf_misc.h" + +int bpf_strcmp(const char *cs, const char *ct) __ksym; +char *bpf_strchr(const char *s, int c) __ksym; +char *bpf_strchrnul(const char *s, int c) __ksym; +char *bpf_strnchr(void *s, u32 s__sz, int c) __ksym; +char *bpf_strnchrnul(void *s, u32 s__sz, int c) __ksym; +char *bpf_strrchr(const char *s, int c) __ksym; +size_t bpf_strlen(const char *s) __ksym; +size_t bpf_strnlen(void *s, u32 s__sz) __ksym; +size_t bpf_strspn(const char *s, const char *accept) __ksym; +size_t bpf_strcspn(const char *s, const char *reject) __ksym; +char *bpf_strpbrk(const char *cs, const char *ct) __ksym; +char *bpf_strstr(const char *s1, const char *s2) __ksym; +char *bpf_strstr(const char *s1, const char *s2) __ksym; +char *bpf_strnstr(void *s1, u32 s1__sz, void *s2, u32 s2__sz) __ksym; + +char str1[] = "hello world"; +char str2[] = "hello"; +char str3[] = "world"; +char str4[] = "abc"; +char str5[] = ""; + +#define __test(retval) SEC("syscall") __success __retval(retval) + +__test(0) int test_strcmp_eq(void *ctx) { return bpf_strcmp(str1, str1); } +__test(1) int test_strcmp_neq(void *ctx) { return bpf_strcmp(str1, str2); } +__test(1) int test_strchr_found(void *ctx) { return bpf_strchr(str1, 'e') - str1; } +__test(11) int test_strchr_null(void *ctx) { return bpf_strchr(str1, '\0') - str1; } +__test(0) u64 test_strchr_notfound(void *ctx) { return (u64)bpf_strchr(str1, 'x'); } +__test(1) int test_strchrnul_found(void *ctx) { return bpf_strchrnul(str1, 'e') - str1; } +__test(11) int test_strchrnul_notfound(void *ctx) { return bpf_strchrnul(str1, 'x') - str1; } +__test(1) int test_strnchr_found(void *ctx) { return bpf_strnchr(str1, 5, 'e') - str1; } +__test(11) int test_strnchr_null(void *ctx) { return bpf_strnchr(str1, 12, '\0') - str1; } +__test(0) u64 test_strnchr_notfound(void *ctx) { return (u64)bpf_strnchr(str1, 5, 'w'); } +__test(1) int test_strnchrnul_found(void *ctx) { return bpf_strnchrnul(str1, 5, 'e') - str1; } +__test(11) int test_strnchrnul_notfound(void *ctx) { return bpf_strnchrnul(str1, 12, 'x') - str1; } +__test(9) int test_strrchr_found(void *ctx) { return bpf_strrchr(str1, 'l') - str1; } +__test(0) u64 test_strrchr_notfound(void *ctx) { return (u64)bpf_strrchr(str1, 'x'); } +__test(11) size_t test_strlen(void *ctx) { return bpf_strlen(str1); } +__test(11) size_t test_strnlen(void *ctx) { return bpf_strnlen(str1, 12); } +__test(5) size_t test_strspn(void *ctx) { return bpf_strspn(str1, str2); } +__test(2) size_t test_strcspn(void *ctx) { return bpf_strcspn(str1, str3); } +__test(2) int test_strpbrk_found(void *ctx) { return bpf_strpbrk(str1, str3) - str1; } +__test(0) u64 test_strpbrk_notfound(void *ctx) { return (u64)bpf_strpbrk(str1, str4); } +__test(6) int test_strstr_found(void *ctx) { return bpf_strstr(str1, str3) - str1; } +__test(0) u64 test_strstr_notfound(void *ctx) { return (u64)bpf_strstr(str1, str4); } +__test(0) int test_strstr_empty(void *ctx) { return bpf_strstr(str1, str5) - str1; } +__test(6) int test_strnstr_found(void *ctx) { return bpf_strnstr(str1, 12, str3, 6) - str1; } +__test(0) u64 test_strnstr_unsafe(void *ctx) { return (u64)bpf_strnstr(str1, 5, str3, 5); } +__test(0) u64 test_strnstr_notfound(void *ctx) { return (u64)bpf_strnstr(str1, 12, str4, 4); } +__test(0) int test_strnstr_empty(void *ctx) { return bpf_strnstr(str1, 5, str5, 1) - str1; } + +char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/string_kfuncs_bench.c b/tools/testing/selftests/bpf/progs/string_kfuncs_bench.c new file mode 100644 index 0000000000000..e227c54a5b929 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/string_kfuncs_bench.c @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2025. Red Hat, Inc. */ +#include "vmlinux.h" +#include +#include + +#define STR_SZ 4096 + +size_t bpf_strlen(const char *s) __ksym; +size_t bpf_strnlen(void *s, u32 s__sz) __ksym; +char *bpf_strchr(const char *s, int c) __ksym; +char *bpf_strnchr(void *s, u32 s__sz, int c) __ksym; +char *bpf_strchrnul(const char *s, int c) __ksym; +char *bpf_strnchrnul(void *s, u32 s__sz, int c) __ksym; +char *bpf_strstr(const char *s1, const char *s2) __ksym; +char *bpf_strnstr(void *s1, u32 s1__sz, void *s2, u32 s2__sz) __ksym; + +/* Will be updated by benchmark before program loading */ +const volatile unsigned int str_len = 1; +long hits = 0; +char str[STR_SZ]; +char substr[STR_SZ]; + +char _license[] SEC("license") = "GPL"; + +SEC("tp/syscalls/sys_enter_getpgid") +int strlen_bench(void *ctx) +{ + if (bpf_strlen(str) > 0) + __sync_add_and_fetch(&hits, 1); + return 0; +} + +SEC("tp/syscalls/sys_enter_getpgid") +int strnlen_bench(void *ctx) +{ + if (bpf_strnlen(str, str_len + 1) > 0) + __sync_add_and_fetch(&hits, 1); + return 0; +} + +SEC("tp/syscalls/sys_enter_getpgid") +int strchr_bench(void *ctx) +{ + if (bpf_strchr(str, '0') != NULL) + __sync_add_and_fetch(&hits, 1); + return 0; +} + +SEC("tp/syscalls/sys_enter_getpgid") +int strnchr_bench(void *ctx) +{ + if (bpf_strnchr(str, str_len + 1, '0') != NULL) + __sync_add_and_fetch(&hits, 1); + return 0; +} + +SEC("tp/syscalls/sys_enter_getpgid") +int strchrnul_bench(void *ctx) +{ + if (*bpf_strchrnul(str, '0') != '\0') + __sync_add_and_fetch(&hits, 1); + return 0; +} + +SEC("tp/syscalls/sys_enter_getpgid") +int strnchrnul_bench(void *ctx) +{ + if (*bpf_strnchrnul(str, str_len + 1, '0') != '\0') + __sync_add_and_fetch(&hits, 1); + return 0; +} + +SEC("tp/syscalls/sys_enter_getpgid") +int strstr_bench(void *ctx) +{ + if (bpf_strstr(str, substr) != NULL) + __sync_add_and_fetch(&hits, 1); + return 0; +} + +SEC("tp/syscalls/sys_enter_getpgid") +int strnstr_bench(void *ctx) +{ + if (bpf_strnstr(str, str_len + 1, substr, str_len / 4 + 1) != NULL) + __sync_add_and_fetch(&hits, 1); + return 0; +}