From: Kumar Kartikeya Dwivedi Global subprograms returning a 16-byte scalar use R0:R2. The verifier therefore checks both registers at every ordinary and unwind exit. LLVM lowers a resume to "r0 = 0; unwind_exit". If cleanup code does not happen to initialize R2, the verifier rejects the unwind path with: R2 !read_ok Extend unwind_cxx with a global unsigned __int128-returning subprogram whose cleanup only releases its RCU read lock. Load it separately so the existing cases continue to run and the failure is attributed to this case. Check both the normal aggregate return and caught exception. The test passes once LLVM defines R2 before emitting unwind_exit. Signed-off-by: Kumar Kartikeya Dwivedi Signed-off-by: Alexei Starovoitov --- .../selftests/bpf/prog_tests/unwind_cxx.c | 24 ++++++++++++- .../selftests/bpf/progs/unwind_cxx.cpp | 35 +++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/bpf/prog_tests/unwind_cxx.c b/tools/testing/selftests/bpf/prog_tests/unwind_cxx.c index 36e52766022c..23e9c7566563 100644 --- a/tools/testing/selftests/bpf/prog_tests/unwind_cxx.c +++ b/tools/testing/selftests/bpf/prog_tests/unwind_cxx.c @@ -42,7 +42,8 @@ void test_unwind_cxx(void) { const char *file = "./unwind_cxx.bpf.o"; struct bpf_object *obj; - int err; + struct bpf_program *prog; + int err = -EINVAL; if (access(file, R_OK)) { printf("%s:SKIP:gcc doesn't build %s\n", __func__, file); @@ -57,6 +58,10 @@ void test_unwind_cxx(void) obj = bpf_object__open_file(file, NULL); if (!ASSERT_OK_PTR(obj, "open")) return; + prog = bpf_object__find_program_by_name(obj, "catches_aggregate"); + if (!ASSERT_OK_PTR(prog, "catches_aggregate")) + goto out; + bpf_program__set_autoload(prog, false); err = bpf_object__load(obj); if (err == -ENOTSUPP) { printf("%s:SKIP:JIT does not support unwinding\n", __func__); @@ -73,4 +78,21 @@ void test_unwind_cxx(void) run(obj, "does_not_catch", 200, 0, 12); out: bpf_object__close(obj); + if (err) + return; + + if (!test__start_subtest("aggregate_return")) + return; + obj = bpf_object__open_file(file, NULL); + if (!ASSERT_OK_PTR(obj, "open_aggregate")) + return; + bpf_object__for_each_program(prog, obj) + bpf_program__set_autoload(prog, + strcmp(bpf_program__name(prog), "catches_aggregate") == 0); + if (!ASSERT_OK(bpf_object__load(obj), "load_aggregate")) + goto out_aggregate; + run(obj, "catches_aggregate", 64, 71, 0); + run(obj, "catches_aggregate", 200, 42, 0); +out_aggregate: + bpf_object__close(obj); } diff --git a/tools/testing/selftests/bpf/progs/unwind_cxx.cpp b/tools/testing/selftests/bpf/progs/unwind_cxx.cpp index d46ee41fef85..3a113ffea29c 100644 --- a/tools/testing/selftests/bpf/progs/unwind_cxx.cpp +++ b/tools/testing/selftests/bpf/progs/unwind_cxx.cpp @@ -67,6 +67,17 @@ struct rcu_guard { } }; +struct plain_rcu_guard { + __always_inline plain_rcu_guard() + { + bpf_rcu_read_lock(); + } + __always_inline ~plain_rcu_guard() + { + bpf_rcu_read_unlock(); + } +}; + struct preempt_guard { __always_inline preempt_guard() { @@ -95,6 +106,18 @@ static __noinline int middle(int len) return inner(len) + 1; } +using u128 = unsigned __int128; + +/* Global subprogs returning 16 bytes use R0:R2, including unwind exits. */ +extern "C" __noinline u128 aggregate_return(int len) +{ + plain_rcu_guard guard; + + if (len > 100) + throw 1; + return (static_cast(len) << 64) | 7; +} + extern "C" SEC("tc") int catches(struct __sk_buff *skb) { try { @@ -110,6 +133,18 @@ extern "C" SEC("tc") int does_not_catch(struct __sk_buff *skb) return middle(skb->len); } +extern "C" SEC("tc") int catches_aggregate(struct __sk_buff *skb) +{ + try { + u128 value = aggregate_return(skb->len); + + return static_cast(value) + + static_cast(value >> 64); + } catch (...) { + return 42; + } +} + #endif char _license[] SEC("license") = "GPL"; -- 2.55.0