From: Yonghong Song <yonghong.song@linux.dev>
To: bpf@vger.kernel.org
Cc: Alexei Starovoitov <ast@kernel.org>,
Andrii Nakryiko <andrii@kernel.org>,
Daniel Borkmann <daniel@iogearbox.net>,
Eduard Zingerman <eddyz87@gmail.com>,
kernel-team@fb.com
Subject: [PATCH bpf-next 4/5] selftests/bpf: Test trampoline args around a 16-byte aligned stack arg
Date: Thu, 8 Oct 2026 23:42:28 -0700 [thread overview]
Message-ID: <20261009064228.1240628-1-yonghong.song@linux.dev> (raw)
In-Reply-To: <20261009064206.1238047-1-yonghong.song@linux.dev>
Add two bpf_testmod functions whose 16-byte aligned argument, a struct
holding an __int128, is passed on the stack after a hole:
- bpf_testmod_test_int128_stack(): seven u64s before the struct, so it
leaves a register empty on arm64 and a stack slot empty on x86-64.
- bpf_testmod_test_int128_backfill(): a 16-byte struct of two longs
moves to the x86-64 stack and gives R9 back to the next argument; the
struct then leaves a stack slot empty on both x86-64 and arm64.
The __int128 is wrapped in a struct because btf_ctx_access() refuses a
program reading an integer argument wider than eight bytes, while it
lets the program read each slot of a struct argument.
The fentry programs read each argument slot from ctx[] and check that
the trampoline packs them in the BPF convention, and the fexit programs
check the return value, weighted by argument slot, which is only right
if the original function got its arguments back where its convention
wants them.
Without the preceding x86 fix the test fails with:
test_int128_args:FAIL:s:h.lo unexpected s:h.lo: actual 2 != expected 8
...
test_int128_args:FAIL:s ret unexpected s ret: actual 285 != expected 385
Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
---
.../selftests/bpf/prog_tests/tracing_struct.c | 22 +++++++--
.../bpf/progs/tracing_struct_int128.c | 45 +++++++++++++++++++
.../selftests/bpf/test_kmods/bpf_testmod.c | 43 ++++++++++++++++++
3 files changed, 107 insertions(+), 3 deletions(-)
diff --git a/tools/testing/selftests/bpf/prog_tests/tracing_struct.c b/tools/testing/selftests/bpf/prog_tests/tracing_struct.c
index 15b95d0235b5..eced07a61dde 100644
--- a/tools/testing/selftests/bpf/prog_tests/tracing_struct.c
+++ b/tools/testing/selftests/bpf/prog_tests/tracing_struct.c
@@ -117,9 +117,11 @@ static void test_int128_args(void)
{
/*
* __int128 arguments are passed in a register pair on x86_64 and
- * arm64, which the trampoline packs into two context slots. Other
- * architectures pass a __int128 differently (e.g. s390x passes larger
- * arguments by reference), so only exercise this on x86_64 and arm64.
+ * arm64, which the trampoline packs into two context slots. Past the
+ * registers, both start an argument aligned to 16 bytes at an even
+ * stack slot, which the trampoline packs too. Other architectures
+ * pass a __int128 differently (e.g. s390x passes larger arguments by
+ * reference), so only exercise this on x86_64 and arm64.
*/
#if defined(__x86_64__) || defined(__aarch64__)
struct tracing_struct_int128 *skel;
@@ -139,6 +141,20 @@ static void test_int128_args(void)
ASSERT_EQ(skel->bss->t_c, 3, "t:c");
ASSERT_EQ(skel->bss->t_ret, 6, "t ret");
+ ASSERT_EQ(skel->bss->s_g, 7, "s:g");
+ ASSERT_EQ(skel->bss->s_h_lo, 8, "s:h.lo");
+ ASSERT_EQ(skel->bss->s_h_hi, 9, "s:h.hi");
+ ASSERT_EQ(skel->bss->s_i, 10, "s:i");
+ ASSERT_EQ(skel->bss->s_ret, 385, "s ret");
+
+ ASSERT_EQ(skel->bss->b_f_a, 6, "b:f.a");
+ ASSERT_EQ(skel->bss->b_f_b, 7, "b:f.b");
+ ASSERT_EQ(skel->bss->b_g, 8, "b:g");
+ ASSERT_EQ(skel->bss->b_h, 9, "b:h");
+ ASSERT_EQ(skel->bss->b_i_lo, 10, "b:i.lo");
+ ASSERT_EQ(skel->bss->b_i_hi, 11, "b:i.hi");
+ ASSERT_EQ(skel->bss->b_ret, 506, "b ret");
+
destroy_skel:
tracing_struct_int128__destroy(skel);
#else
diff --git a/tools/testing/selftests/bpf/progs/tracing_struct_int128.c b/tools/testing/selftests/bpf/progs/tracing_struct_int128.c
index 4638dfec1f38..5520ee500c01 100644
--- a/tools/testing/selftests/bpf/progs/tracing_struct_int128.c
+++ b/tools/testing/selftests/bpf/progs/tracing_struct_int128.c
@@ -15,4 +15,49 @@ int test_int128_arg_fexit(unsigned long long *ctx)
return 0;
}
+/*
+ * The arguments come packed in the BPF convention whatever holes the
+ * kernel convention left between them: an eightbyte each, and two for the
+ * struct holding an __int128.
+ */
+long s_g, s_h_lo, s_h_hi, s_i, s_ret;
+
+SEC("fentry/bpf_testmod_test_int128_stack")
+int test_int128_stack_fentry(unsigned long long *ctx)
+{
+ s_g = ctx[6];
+ s_h_lo = ctx[7];
+ s_h_hi = ctx[8];
+ s_i = ctx[9];
+ return 0;
+}
+
+SEC("fexit/bpf_testmod_test_int128_stack")
+int test_int128_stack_fexit(unsigned long long *ctx)
+{
+ s_ret = ctx[10];
+ return 0;
+}
+
+long b_f_a, b_f_b, b_g, b_h, b_i_lo, b_i_hi, b_ret;
+
+SEC("fentry/bpf_testmod_test_int128_backfill")
+int test_int128_backfill_fentry(unsigned long long *ctx)
+{
+ b_f_a = ctx[5];
+ b_f_b = ctx[6];
+ b_g = ctx[7];
+ b_h = ctx[8];
+ b_i_lo = ctx[9];
+ b_i_hi = ctx[10];
+ return 0;
+}
+
+SEC("fexit/bpf_testmod_test_int128_backfill")
+int test_int128_backfill_fexit(unsigned long long *ctx)
+{
+ b_ret = ctx[11];
+ return 0;
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
index 54a5fc39e0e2..23a2c88d3e7c 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -197,6 +197,43 @@ bpf_testmod_test_int128_arg(__int128 a, int b, long c)
bpf_testmod_test_struct_arg_result = (long)a + b + c;
return bpf_testmod_test_struct_arg_result;
}
+
+struct bpf_testmod_struct_int128 {
+ __int128 a;
+};
+
+/*
+ * x86-64 puts g in the first stack slot and h, aligned to 16 bytes, in
+ * the third and fourth, leaving a hole. arm64 leaves x7 empty and puts h
+ * in the first two stack slots. Either way h and i are not where the BPF
+ * convention packs them, right after g.
+ */
+noinline long
+bpf_testmod_test_int128_stack(u64 a, u64 b, u64 c, u64 d, u64 e, u64 f,
+ u64 g, struct bpf_testmod_struct_int128 h, u64 i)
+{
+ bpf_testmod_test_struct_arg_result = a + b * 2 + c * 3 + d * 4 + e * 5 +
+ f * 6 + g * 7 + (u64)h.a * 8 +
+ (u64)((unsigned __int128)h.a >> 64) * 9 + i * 10;
+ return bpf_testmod_test_struct_arg_result;
+}
+
+/*
+ * x86-64 has no register left for f after e, so f goes to the stack and g
+ * takes R9 back; h goes to the third stack slot and i, aligned to 16
+ * bytes, to the fifth and sixth. arm64 has registers up to g, puts h in
+ * the first stack slot and i in the third and fourth.
+ */
+noinline long
+bpf_testmod_test_int128_backfill(u64 a, u64 b, u64 c, u64 d, u64 e,
+ struct bpf_testmod_struct_arg_2 f, u64 g, u64 h,
+ struct bpf_testmod_struct_int128 i)
+{
+ bpf_testmod_test_struct_arg_result = a + b * 2 + c * 3 + d * 4 + e * 5 +
+ f.a * 6 + f.b * 7 + g * 8 + h * 9 + (u64)i.a * 10 +
+ (u64)((unsigned __int128)i.a >> 64) * 11;
+ return bpf_testmod_test_struct_arg_result;
+}
#endif
__weak noinline void bpf_testmod_looooooooooooooooooooooooooooooong_name(void)
@@ -683,6 +720,12 @@ bpf_testmod_test_read(struct file *file, struct kobject *kobj,
#ifdef __SIZEOF_INT128__
(void)bpf_testmod_test_int128_ret(i);
(void)bpf_testmod_test_int128_arg((__int128)1, 2, 3);
+ (void)bpf_testmod_test_int128_stack(1, 2, 3, 4, 5, 6, 7,
+ (struct bpf_testmod_struct_int128){ .a = ((__int128)9 << 64) | 8 },
+ 10);
+ (void)bpf_testmod_test_int128_backfill(1, 2, 3, 4, 5,
+ (struct bpf_testmod_struct_arg_2){ .a = 6, .b = 7 }, 8, 9,
+ (struct bpf_testmod_struct_int128){ .a = ((__int128)11 << 64) | 10 });
#endif
(void)trace_bpf_testmod_test_raw_tp_null_tp(NULL);
--
2.53.0-Meta
next prev parent reply other threads:[~2026-10-09 6:42 UTC|newest]
Thread overview: 18+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-09 6:42 [PATCH bpf-next 0/5] bpf: Follow up by-value struct and __int128 argument support Yonghong Song
2026-10-09 6:42 ` [PATCH bpf-next 1/5] selftests/bpf: Test by-value __int128 kfunc arguments at an odd slot Yonghong Song
2026-10-09 7:48 ` bot+bpf-ci
2026-10-09 17:04 ` Yonghong Song
2026-10-09 6:42 ` [PATCH bpf-next 2/5] bpf: Keep the BPF calling convention for a trampoline on a BPF function Yonghong Song
2026-10-09 7:26 ` bot+bpf-ci
2026-10-09 17:20 ` Yonghong Song
2026-10-10 15:00 ` Alexei Starovoitov
2026-10-12 6:03 ` Yonghong Song
2026-10-09 6:42 ` [PATCH bpf-next 3/5] bpf, x86: Place trampoline arguments by the x86-64 calling convention Yonghong Song
2026-10-09 7:08 ` sashiko-bot
2026-10-09 17:38 ` Yonghong Song
2026-10-09 6:42 ` Yonghong Song [this message]
2026-10-09 6:51 ` [PATCH bpf-next 4/5] selftests/bpf: Test trampoline args around a 16-byte aligned stack arg sashiko-bot
2026-10-09 18:12 ` Yonghong Song
2026-10-09 6:42 ` [PATCH bpf-next 5/5] selftests/bpf: Test trampoline args of a BPF function at an odd slot Yonghong Song
2026-10-09 7:48 ` bot+bpf-ci
2026-10-09 18:17 ` Yonghong Song
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261009064228.1240628-1-yonghong.song@linux.dev \
--to=yonghong.song@linux.dev \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=kernel-team@fb.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.