* [PATCH bpf-next 0/2] bpf: enable x86 fentry on tail-called programs @ 2026-03-27 14:16 Takeru Hayasaka 2026-03-27 14:16 ` [PATCH bpf-next 1/2] bpf, x86: patch tail-call fentry slot on non-IBT JITs Takeru Hayasaka 2026-03-27 14:16 ` [PATCH bpf-next 2/2] selftests/bpf: cover fentry on tailcalled programs Takeru Hayasaka 0 siblings, 2 replies; 15+ messages in thread From: Takeru Hayasaka @ 2026-03-27 14:16 UTC (permalink / raw) To: ast, daniel, andrii; +Cc: bpf, x86, linux-kselftest, linux-kernel This series enables fentry on x86 BPF programs reached via tail calls and adds a focused selftest for the expected behavior. Patch 1 fixes the x86 mirrored text-poke lookup so the tail-call landing slot is patched on both IBT and non-IBT JITs. Patch 2 adds a selftest that checks both direct entry and tail-called entry for a tailcall callee with fentry attached. Takeru Hayasaka (2): bpf, x86: patch tail-call fentry slot on non-IBT JITs selftests/bpf: cover fentry on tailcalled programs arch/x86/net/bpf_jit_comp.c | 47 +++++++- .../selftests/bpf/prog_tests/tailcalls.c | 110 ++++++++++++++++++ .../bpf/progs/tailcall_fentry_probe.c | 16 +++ .../bpf/progs/tailcall_fentry_target.c | 27 +++++ 4 files changed, 197 insertions(+), 3 deletions(-) create mode 100644 tools/testing/selftests/bpf/progs/tailcall_fentry_probe.c create mode 100644 tools/testing/selftests/bpf/progs/tailcall_fentry_target.c -- 2.43.0 ^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH bpf-next 1/2] bpf, x86: patch tail-call fentry slot on non-IBT JITs 2026-03-27 14:16 [PATCH bpf-next 0/2] bpf: enable x86 fentry on tail-called programs Takeru Hayasaka @ 2026-03-27 14:16 ` Takeru Hayasaka 2026-03-27 14:24 ` Alexei Starovoitov 2026-03-27 14:16 ` [PATCH bpf-next 2/2] selftests/bpf: cover fentry on tailcalled programs Takeru Hayasaka 1 sibling, 1 reply; 15+ messages in thread From: Takeru Hayasaka @ 2026-03-27 14:16 UTC (permalink / raw) To: ast, daniel, andrii; +Cc: bpf, x86, linux-kselftest, linux-kernel x86 tail-call fentry patching mirrors CALL text pokes to the tail-call landing slot. The helper that locates that mirrored slot assumes an ENDBR-prefixed landing, which works on IBT JITs but fails on non-IBT JITs where the landing starts directly with the 5-byte patch slot. As a result, the regular entry gets patched but the tail-call landing remains NOP5, so fentry never fires for tail-called programs on non-IBT kernels. Anchor the lookup on the landing address, verify the short-jump layout first, and only check ENDBR when one is actually emitted. Signed-off-by: Takeru Hayasaka <hayatake396@gmail.com> --- arch/x86/net/bpf_jit_comp.c | 47 ++++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c index e9b78040d703..fe5fd37f65d8 100644 --- a/arch/x86/net/bpf_jit_comp.c +++ b/arch/x86/net/bpf_jit_comp.c @@ -325,8 +325,10 @@ struct jit_context { /* Number of bytes emit_patch() needs to generate instructions */ #define X86_PATCH_SIZE 5 +/* Number of bytes used by the short jump that skips the tail-call hook. */ +#define X86_TAIL_CALL_SKIP_JMP_SIZE 2 /* Number of bytes that will be skipped on tailcall */ -#define X86_TAIL_CALL_OFFSET (12 + ENDBR_INSN_SIZE) +#define X86_TAIL_CALL_OFFSET (12 + X86_TAIL_CALL_SKIP_JMP_SIZE + ENDBR_INSN_SIZE) static void push_r9(u8 **pprog) { @@ -545,8 +547,15 @@ static void emit_prologue(u8 **pprog, u8 *ip, u32 stack_depth, bool ebpf_from_cb EMIT3(0x48, 0x89, 0xE5); /* mov rbp, rsp */ } + if (!is_subprog) { + /* Normal entry skips the tail-call-only trampoline hook. */ + EMIT2(0xEB, ENDBR_INSN_SIZE + X86_PATCH_SIZE); + } + /* X86_TAIL_CALL_OFFSET is here */ EMIT_ENDBR(); + if (!is_subprog) + emit_nops(&prog, X86_PATCH_SIZE); /* sub rsp, rounded_stack_depth */ if (stack_depth) @@ -632,12 +641,33 @@ static int __bpf_arch_text_poke(void *ip, enum bpf_text_poke_type old_t, return ret; } +static void *bpf_tail_call_fentry_ip(void *ip) +{ + u8 *tail_ip = ip + X86_TAIL_CALL_OFFSET; + u8 *landing = tail_ip - ENDBR_INSN_SIZE; + + /* ip points at the regular fentry slot after the entry ENDBR. */ + if (landing[-X86_TAIL_CALL_SKIP_JMP_SIZE] != 0xEB || + landing[-X86_TAIL_CALL_SKIP_JMP_SIZE + 1] != + ENDBR_INSN_SIZE + X86_PATCH_SIZE) + return NULL; + + if (ENDBR_INSN_SIZE && !is_endbr((u32 *)landing)) + return NULL; + + return tail_ip; +} + int bpf_arch_text_poke(void *ip, enum bpf_text_poke_type old_t, enum bpf_text_poke_type new_t, void *old_addr, void *new_addr) { + void *tail_ip = NULL; + bool is_bpf_text = is_bpf_text_address((long)ip); + int ret, tail_ret; + if (!is_kernel_text((long)ip) && - !is_bpf_text_address((long)ip)) + !is_bpf_text) /* BPF poking in modules is not supported */ return -EINVAL; @@ -648,7 +678,18 @@ int bpf_arch_text_poke(void *ip, enum bpf_text_poke_type old_t, if (is_endbr(ip)) ip += ENDBR_INSN_SIZE; - return __bpf_arch_text_poke(ip, old_t, new_t, old_addr, new_addr); + if (is_bpf_text && (old_t == BPF_MOD_CALL || new_t == BPF_MOD_CALL)) + tail_ip = bpf_tail_call_fentry_ip(ip); + + ret = __bpf_arch_text_poke(ip, old_t, new_t, old_addr, new_addr); + if (ret < 0 || !tail_ip) + return ret; + + tail_ret = __bpf_arch_text_poke(tail_ip, old_t, new_t, old_addr, new_addr); + if (tail_ret < 0) + return tail_ret; + + return ret && tail_ret; } #define EMIT_LFENCE() EMIT3(0x0F, 0xAE, 0xE8) -- 2.43.0 ^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [PATCH bpf-next 1/2] bpf, x86: patch tail-call fentry slot on non-IBT JITs 2026-03-27 14:16 ` [PATCH bpf-next 1/2] bpf, x86: patch tail-call fentry slot on non-IBT JITs Takeru Hayasaka @ 2026-03-27 14:24 ` Alexei Starovoitov 2026-03-27 15:12 ` Takeru Hayasaka 0 siblings, 1 reply; 15+ messages in thread From: Alexei Starovoitov @ 2026-03-27 14:24 UTC (permalink / raw) To: Takeru Hayasaka Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, bpf, X86 ML, open list:KERNEL SELFTEST FRAMEWORK, LKML On Fri, Mar 27, 2026 at 7:16 AM Takeru Hayasaka <hayatake396@gmail.com> wrote: > > x86 tail-call fentry patching mirrors CALL text pokes to the tail-call > landing slot. > > The helper that locates that mirrored slot assumes an ENDBR-prefixed > landing, which works on IBT JITs but fails on non-IBT JITs where the > landing starts directly with the 5-byte patch slot. tailcalls are deprecated. We should go the other way and disable them ibt jit instead. The less interaction between fentry and tailcall the better. pw-bot: cr ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH bpf-next 1/2] bpf, x86: patch tail-call fentry slot on non-IBT JITs 2026-03-27 14:24 ` Alexei Starovoitov @ 2026-03-27 15:12 ` Takeru Hayasaka 2026-03-27 15:21 ` Alexei Starovoitov 0 siblings, 1 reply; 15+ messages in thread From: Takeru Hayasaka @ 2026-03-27 15:12 UTC (permalink / raw) To: Alexei Starovoitov Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, bpf, X86 ML, open list:KERNEL SELFTEST FRAMEWORK, LKML Hi Alexei Thanks, and Sorry, I sent an older changelog from while I was still iterating on this, and it described the issue incorrectly. My changelog made this sound like an IBT/non-IBT-specific issue, but that was wrong. On current kernels, fentry on tail-called programs is not supported in either case. Only the regular fentry patch site is patched; there is no tail-call landing patching in either case, so disabling IBT does not make it work. What this series was trying to do was add support for fentry on tail-called x86 programs. The non-IBT part was only about a bug in my initial implementation of that support, not the underlying motivation. The motivation is observability of existing tailcall-heavy BPF/XDP programs, where tail-called leaf programs are currently a blind spot for fentry-based debugging. If supporting fentry on tail-called programs is still not something you'd want upstream, I understand. If I resend this, I'll fix the changelog/cover letter to describe it correctly. ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH bpf-next 1/2] bpf, x86: patch tail-call fentry slot on non-IBT JITs 2026-03-27 15:12 ` Takeru Hayasaka @ 2026-03-27 15:21 ` Alexei Starovoitov 2026-03-27 15:44 ` Takeru Hayasaka 0 siblings, 1 reply; 15+ messages in thread From: Alexei Starovoitov @ 2026-03-27 15:21 UTC (permalink / raw) To: Takeru Hayasaka Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, bpf, X86 ML, open list:KERNEL SELFTEST FRAMEWORK, LKML On Fri, Mar 27, 2026 at 8:12 AM Takeru Hayasaka <hayatake396@gmail.com> wrote: > > Hi Alexei > > Thanks, and Sorry, I sent an older changelog from while I was still > iterating on this, and it described the issue incorrectly. > > My changelog made this sound like an IBT/non-IBT-specific issue, but > that was wrong. On current kernels, fentry on tail-called programs is > not supported in either case. Only the regular fentry patch site is > patched; there is no tail-call landing patching in either case, so > disabling IBT does not make it work. > > What this series was trying to do was add support for fentry on > tail-called x86 programs. The non-IBT part was only about a bug in my > initial implementation of that support, not the underlying motivation. > > The motivation is observability of existing tailcall-heavy BPF/XDP > programs, where tail-called leaf programs are currently a blind spot for > fentry-based debugging. I get that, but I'd rather not open this can of worms. We had enough headaches when tailcalls, fentry, subprogs are combined. Like this set: https://lore.kernel.org/all/20230912150442.2009-1-hffilwlqm@gmail.com/ and the followups. ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH bpf-next 1/2] bpf, x86: patch tail-call fentry slot on non-IBT JITs 2026-03-27 15:21 ` Alexei Starovoitov @ 2026-03-27 15:44 ` Takeru Hayasaka 2026-03-27 15:58 ` Alexei Starovoitov 0 siblings, 1 reply; 15+ messages in thread From: Takeru Hayasaka @ 2026-03-27 15:44 UTC (permalink / raw) To: Alexei Starovoitov Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, bpf, X86 ML, open list:KERNEL SELFTEST FRAMEWORK, LKML Understood. I was a bit surprised to read that this area ended up taking months of follow-up work.... One thing I am still trying to understand is what the preferred debuggability/observability direction would be for existing tailcall-heavy BPF/XDP deployments. Tail calls are already used in practice as a program decomposition mechanism, especially in XDP pipelines, and that leaves tail-called leaf programs harder to observe today. If fentry on tail-called programs is not something you'd want upstream, is there another direction you would recommend for improving observability/debuggability of such existing deployments? 2026年3月28日(土) 0:21 Alexei Starovoitov <alexei.starovoitov@gmail.com>: > > On Fri, Mar 27, 2026 at 8:12 AM Takeru Hayasaka <hayatake396@gmail.com> wrote: > > > > Hi Alexei > > > > Thanks, and Sorry, I sent an older changelog from while I was still > > iterating on this, and it described the issue incorrectly. > > > > My changelog made this sound like an IBT/non-IBT-specific issue, but > > that was wrong. On current kernels, fentry on tail-called programs is > > not supported in either case. Only the regular fentry patch site is > > patched; there is no tail-call landing patching in either case, so > > disabling IBT does not make it work. > > > > What this series was trying to do was add support for fentry on > > tail-called x86 programs. The non-IBT part was only about a bug in my > > initial implementation of that support, not the underlying motivation. > > > > The motivation is observability of existing tailcall-heavy BPF/XDP > > programs, where tail-called leaf programs are currently a blind spot for > > fentry-based debugging. > > I get that, but I'd rather not open this can of worms. > We had enough headaches when tailcalls, fentry, subprogs are combined. > Like this set: > https://lore.kernel.org/all/20230912150442.2009-1-hffilwlqm@gmail.com/ > and the followups. ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH bpf-next 1/2] bpf, x86: patch tail-call fentry slot on non-IBT JITs 2026-03-27 15:44 ` Takeru Hayasaka @ 2026-03-27 15:58 ` Alexei Starovoitov 2026-03-27 16:06 ` Takeru Hayasaka 0 siblings, 1 reply; 15+ messages in thread From: Alexei Starovoitov @ 2026-03-27 15:58 UTC (permalink / raw) To: Takeru Hayasaka Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, bpf, X86 ML, open list:KERNEL SELFTEST FRAMEWORK, LKML On Fri, Mar 27, 2026 at 8:45 AM Takeru Hayasaka <hayatake396@gmail.com> wrote: > > Understood. I was a bit surprised to read that this area ended up taking > months of follow-up work.... > > One thing I am still trying to understand is what the preferred > debuggability/observability direction would be for existing > tailcall-heavy BPF/XDP deployments. > > Tail calls are already used in practice as a program decomposition > mechanism, especially in XDP pipelines, and that leaves tail-called leaf > programs harder to observe today. > > If fentry on tail-called programs is not something you'd want upstream, > is there another direction you would recommend for improving > observability/debuggability of such existing deployments? You don't need fentry to debug. perf works just fine on all bpf progs whether tailcall or not. Also pls don't top post. ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH bpf-next 1/2] bpf, x86: patch tail-call fentry slot on non-IBT JITs 2026-03-27 15:58 ` Alexei Starovoitov @ 2026-03-27 16:06 ` Takeru Hayasaka 2026-03-27 16:09 ` Alexei Starovoitov 0 siblings, 1 reply; 15+ messages in thread From: Takeru Hayasaka @ 2026-03-27 16:06 UTC (permalink / raw) To: Alexei Starovoitov Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, bpf, X86 ML, open list:KERNEL SELFTEST FRAMEWORK, LKML Sorry about the top-posting. That makes sense, thanks. I agree perf can provide visibility into which BPF programs are running, including tail-called ones. What I am still unsure about is packet-level / structured-data observability. My use case is closer to xdpdump-style debugging, where I want to inspect packet-related context from specific XDP leaf programs in a live pipeline. That feels harder to express with perf alone, so I am trying to understand what the preferred direction would be for that kind of use case in tailcall-heavy XDP deployments. ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH bpf-next 1/2] bpf, x86: patch tail-call fentry slot on non-IBT JITs 2026-03-27 16:06 ` Takeru Hayasaka @ 2026-03-27 16:09 ` Alexei Starovoitov 2026-03-27 16:30 ` Takeru Hayasaka 0 siblings, 1 reply; 15+ messages in thread From: Alexei Starovoitov @ 2026-03-27 16:09 UTC (permalink / raw) To: Takeru Hayasaka Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, bpf, X86 ML, open list:KERNEL SELFTEST FRAMEWORK, LKML On Fri, Mar 27, 2026 at 9:06 AM Takeru Hayasaka <hayatake396@gmail.com> wrote: > > Sorry about the top-posting. yet you're still top posting :( > That makes sense, thanks. I agree perf can provide visibility into which > BPF programs are running, including tail-called ones. > > What I am still unsure about is packet-level / structured-data > observability. My use case is closer to xdpdump-style debugging, where I > want to inspect packet-related context from specific XDP leaf programs > in a live pipeline. see how cilium did it. with pwru tool, etc. ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH bpf-next 1/2] bpf, x86: patch tail-call fentry slot on non-IBT JITs 2026-03-27 16:09 ` Alexei Starovoitov @ 2026-03-27 16:30 ` Takeru Hayasaka 2026-03-30 9:07 ` Leon Hwang 0 siblings, 1 reply; 15+ messages in thread From: Takeru Hayasaka @ 2026-03-27 16:30 UTC (permalink / raw) To: Alexei Starovoitov Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, bpf, X86 ML, open list:KERNEL SELFTEST FRAMEWORK, LKML > yet you're still top posting :( Sorry about that. I misunderstood what top posting meant and ended up replying in the wrong style. I had not understood that it referred to quoting in that way, and I am embarrassed that I got it wrong.... > see how cilium did it. with pwru tool, etc. Thank you for the suggestion. As for pwru, I had thought it was not able to capture packet data such as pcap, and understood it more as a tool to trace where a specific packet enters the processing path and how it is handled. For example, in an environment where systems are already interconnected and running, I sometimes want to capture the actual packets being sent for real processing. On the other hand, if the goal is simply to observe processing safely in a development environment, I think tools such as ipftrace2 or pwru can be very useful. ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH bpf-next 1/2] bpf, x86: patch tail-call fentry slot on non-IBT JITs 2026-03-27 16:30 ` Takeru Hayasaka @ 2026-03-30 9:07 ` Leon Hwang 2026-03-30 16:46 ` Takeru Hayasaka 0 siblings, 1 reply; 15+ messages in thread From: Leon Hwang @ 2026-03-30 9:07 UTC (permalink / raw) To: Takeru Hayasaka, Alexei Starovoitov Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, bpf, X86 ML, open list:KERNEL SELFTEST FRAMEWORK, LKML On 28/3/26 00:30, Takeru Hayasaka wrote: >> see how cilium did it. with pwru tool, etc. > > Thank you for the suggestion. > As for pwru, I had thought it was not able to capture packet data such as pcap, > and understood it more as a tool to trace where a specific packet > enters the processing path and how it is handled. > > For example, in an environment where systems are already > interconnected and running, I sometimes want to capture the actual > packets being sent for real processing. > On the other hand, if the goal is simply to observe processing safely > in a development environment, I think tools such as ipftrace2 or pwru > can be very useful. > Sounds like you are developing/maintaining an XDP project. If so, and the kernel carries the patches in https://lore.kernel.org/all/20230912150442.2009-1-hffilwlqm@gmail.com/, recommend modifying the XDP project using dispatcher like libxdp [1]. Then, you are able to trace the subprogs which aim to run tail calls; meanwhile, you are able to filter packets using pcap-filter, and to output packets using bpf_xdp_output() helper. [1] https://github.com/xdp-project/xdp-tools/blob/main/lib/libxdp/xdp-dispatcher.c.in Thanks, Leon ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH bpf-next 1/2] bpf, x86: patch tail-call fentry slot on non-IBT JITs 2026-03-30 9:07 ` Leon Hwang @ 2026-03-30 16:46 ` Takeru Hayasaka 2026-03-31 2:24 ` Leon Hwang 0 siblings, 1 reply; 15+ messages in thread From: Takeru Hayasaka @ 2026-03-30 16:46 UTC (permalink / raw) To: Leon Hwang Cc: Alexei Starovoitov, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, bpf, X86 ML, open list:KERNEL SELFTEST FRAMEWORK, LKML > Sounds like you are developing/maintaining an XDP project. > > If so, and the kernel carries the patches in > https://lore.kernel.org/all/20230912150442.2009-1-hffilwlqm@gmail.com/, > recommend modifying the XDP project using dispatcher like libxdp [1]. > Then, you are able to trace the subprogs which aim to run tail calls; > meanwhile, you are able to filter packets using pcap-filter, and to > output packets using bpf_xdp_output() helper. > > [1] > https://github.com/xdp-project/xdp-tools/blob/main/lib/libxdp/xdp-dispatcher.c.in Thank you very much for your wonderful comment, Leon. This was the first time I learned that such a mechanism exists. It is a very interesting ecosystem. If I understand correctly, the idea is to invoke a component that dumps pcap data as one of the tail-called components, right? Thank you very much for sharing this idea with me. If I have a chance to write a new XDP program in the future, I would definitely like to try it. On the other hand, I feel that it is somewhat difficult to apply this idea directly to existing codebases, or to cases where the code is written in Go using something like cilium/ebpf. Also, when it comes to code running in production environments, making changes itself can be difficult. For that reason, I prototyped a tool like this. It is something like a middle ground between xdpdump and xdpcap. I built it so that only packets matched by cbpf are sent up through perf, and while testing it, I noticed that it does not work well for targets invoked via tail call. This is what motivated me to send the patch. https://github.com/takehaya/xdp-ninja Once again, thank you for sharing the idea. Takeru ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH bpf-next 1/2] bpf, x86: patch tail-call fentry slot on non-IBT JITs 2026-03-30 16:46 ` Takeru Hayasaka @ 2026-03-31 2:24 ` Leon Hwang 2026-03-31 4:53 ` Takeru Hayasaka 0 siblings, 1 reply; 15+ messages in thread From: Leon Hwang @ 2026-03-31 2:24 UTC (permalink / raw) To: Takeru Hayasaka Cc: Alexei Starovoitov, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, bpf, X86 ML, open list:KERNEL SELFTEST FRAMEWORK, LKML On 31/3/26 00:46, Takeru Hayasaka wrote: >> Sounds like you are developing/maintaining an XDP project. >> >> If so, and the kernel carries the patches in >> https://lore.kernel.org/all/20230912150442.2009-1-hffilwlqm@gmail.com/, >> recommend modifying the XDP project using dispatcher like libxdp [1]. >> Then, you are able to trace the subprogs which aim to run tail calls; >> meanwhile, you are able to filter packets using pcap-filter, and to >> output packets using bpf_xdp_output() helper. >> >> [1] >> https://github.com/xdp-project/xdp-tools/blob/main/lib/libxdp/xdp-dispatcher.c.in > > Thank you very much for your wonderful comment, Leon. > This was the first time I learned that such a mechanism exists. > > It is a very interesting ecosystem. > If I understand correctly, the idea is to invoke a component that > dumps pcap data as one of the tail-called components, right? It is similar to xdp-ninja/xdp-dump. However, this idea has one more step forward: it is to trace the subprogs instead of only the main prog. For example, __noinline int subprog0(struct xdp_md *xdp) { bpf_tail_call_static(xdp, &m, 0); } __noinline int subprog1(struct xdp_md *xdp) { bpf_tail_call_static(xdp, &m, 1); } __noinline int subprog2(struct xdp_md *xdp) { bpf_tail_call_static(xdp, &m, 2); } SEC("xdp") int main(struct xdp_md *xdp) { subprog0(xdp); subprog1(xdp); subprog2(xdp); return XDP_PASS; } All of them, subprog{0,1,2} and main, will be traced. In this idea, it is to inject pcap-filter expression, the cbpf, using elibpcap [1], and to output packets like your xdp-ninja. It works well during the time I maintained an XDP project. [1] https://github.com/jschwinger233/elibpcap > Thank you very much for sharing this idea with me. > If I have a chance to write a new XDP program in the future, I would > definitely like to try it. > > On the other hand, I feel that it is somewhat difficult to apply this > idea directly to existing codebases, or to cases where the code is> written in Go using something like cilium/ebpf. > Also, when it comes to code running in production environments, making > changes itself can be difficult. Correct. If cannot modify the code, and the tail calls are not called inner subprogs, the aforementioned idea is helpless to trace the tail callees. > > For that reason, I prototyped a tool like this. > It is something like a middle ground between xdpdump and xdpcap. > I built it so that only packets matched by cbpf are sent up through > perf, and while testing it, I noticed that it does not work well for > targets invoked via tail call. > This is what motivated me to send the patch. > I have similar idea years ago, a more generic tracer for tail calls. However, as Alexei's concern, I won't post it. > https://github.com/takehaya/xdp-ninja > It looks wonderful. I developed a similar tool, bpfsnoop [1], to trace BPF progs/subprogs and kernel functions with filtering packets/arguments and outputting packets/arguments info. However, it lacks the ability of outputting packets to pcap file. [1] https://github.com/bpfsnoop/bpfsnoop Thanks, Leon > Once again, thank you for sharing the idea. > Takeru ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH bpf-next 1/2] bpf, x86: patch tail-call fentry slot on non-IBT JITs 2026-03-31 2:24 ` Leon Hwang @ 2026-03-31 4:53 ` Takeru Hayasaka 0 siblings, 0 replies; 15+ messages in thread From: Takeru Hayasaka @ 2026-03-31 4:53 UTC (permalink / raw) To: Leon Hwang Cc: Alexei Starovoitov, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, bpf, X86 ML, open list:KERNEL SELFTEST FRAMEWORK, LKML > It is similar to xdp-ninja/xdp-dump. > > However, this idea has one more step forward: it is to trace the > subprogs instead of only the main prog. > > For example, > > __noinline int subprog0(struct xdp_md *xdp) { bpf_tail_call_static(xdp, > &m, 0); } > __noinline int subprog1(struct xdp_md *xdp) { bpf_tail_call_static(xdp, > &m, 1); } > __noinline int subprog2(struct xdp_md *xdp) { bpf_tail_call_static(xdp, > &m, 2); } > SEC("xdp") int main(struct xdp_md *xdp) > { > subprog0(xdp); > subprog1(xdp); > subprog2(xdp); > return XDP_PASS; > } > > All of them, subprog{0,1,2} and main, will be traced. > > In this idea, it is to inject pcap-filter expression, the cbpf, using > elibpcap [1], and to output packets like your xdp-ninja. > > It works well during the time I maintained an XDP project. > > [1] https://github.com/jschwinger233/elibpcap Thank you very much for your kind reply. elibpcap is a very interesting idea as well. I had also considered whether making it __noinline might leave the function prologue intact, so that it could be hooked via trampoline. I actually tried that idea myself, but I had not yet been able to get it working. I had not investigated it in enough detail, but I was suspecting that it might already be expanded by JIT, which could make that approach difficult. That is why I thought it was excellent that you realized it by taking the approach of inserting the logic there in the first place, rather than trying to hook it afterward. I had also been thinking about supporting use cases where packets need to be captured only after some packet-related decision has already been made in the program. For example, there are cases where the value to match depends on runtime state, such as a Session ID that changes whenever a tunnel is established. I think this kind of approach is very helpful for such cases. So, I was very happy to learn that someone else had been thinking about a similar technique. > It looks wonderful. > > I developed a similar tool, bpfsnoop [1], to trace BPF progs/subprogs > and kernel functions with filtering packets/arguments and outputting > packets/arguments info. However, it lacks the ability of outputting > packets to pcap file. > > [1] https://github.com/bpfsnoop/bpfsnoop Also, bpfsnoop looks like a very nice tool. I starred it on GitHub:) Thank you very much again. I am very grateful that you shared such an excellent idea with me. Thanks, Takeru ^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH bpf-next 2/2] selftests/bpf: cover fentry on tailcalled programs 2026-03-27 14:16 [PATCH bpf-next 0/2] bpf: enable x86 fentry on tail-called programs Takeru Hayasaka 2026-03-27 14:16 ` [PATCH bpf-next 1/2] bpf, x86: patch tail-call fentry slot on non-IBT JITs Takeru Hayasaka @ 2026-03-27 14:16 ` Takeru Hayasaka 1 sibling, 0 replies; 15+ messages in thread From: Takeru Hayasaka @ 2026-03-27 14:16 UTC (permalink / raw) To: ast, daniel, andrii; +Cc: bpf, x86, linux-kselftest, linux-kernel Add a small tailcall target/probe pair and a tailcalls subtest that attaches fentry to the callee program. The test first runs the callee directly and checks that fentry fires exactly once, then runs the entry program, tail-calls into the callee and checks that the same fentry also fires exactly once on the tail-call path. This covers both sides of the x86 change: direct entry must not double-fire, while tail-called entry must now be observable. Signed-off-by: Takeru Hayasaka <hayatake396@gmail.com> --- .../selftests/bpf/prog_tests/tailcalls.c | 110 ++++++++++++++++++ .../bpf/progs/tailcall_fentry_probe.c | 16 +++ .../bpf/progs/tailcall_fentry_target.c | 27 +++++ 3 files changed, 153 insertions(+) create mode 100644 tools/testing/selftests/bpf/progs/tailcall_fentry_probe.c create mode 100644 tools/testing/selftests/bpf/progs/tailcall_fentry_target.c diff --git a/tools/testing/selftests/bpf/prog_tests/tailcalls.c b/tools/testing/selftests/bpf/prog_tests/tailcalls.c index 7d534fde0af9..ac05df65b666 100644 --- a/tools/testing/selftests/bpf/prog_tests/tailcalls.c +++ b/tools/testing/selftests/bpf/prog_tests/tailcalls.c @@ -1113,6 +1113,114 @@ static void test_tailcall_bpf2bpf_fentry_entry(void) bpf_object__close(tgt_obj); } +static void test_tailcall_fentry_tailcallee(void) +{ + struct bpf_object *tgt_obj = NULL, *fentry_obj = NULL; + struct bpf_map *prog_array, *data_map; + struct bpf_link *fentry_link = NULL; + struct bpf_program *prog; + int err, map_fd, callee_fd, main_fd, data_fd, i, val; + char buff[128] = {}; + + LIBBPF_OPTS(bpf_test_run_opts, topts, + .data_in = buff, + .data_size_in = sizeof(buff), + .repeat = 1, + ); + + err = bpf_prog_test_load("tailcall_fentry_target.bpf.o", + BPF_PROG_TYPE_SCHED_CLS, + &tgt_obj, &main_fd); + if (!ASSERT_OK(err, "load tgt_obj")) + return; + + prog_array = bpf_object__find_map_by_name(tgt_obj, "jmp_table"); + if (!ASSERT_OK_PTR(prog_array, "find jmp_table map")) + goto out; + + map_fd = bpf_map__fd(prog_array); + if (!ASSERT_FALSE(map_fd < 0, "find jmp_table map fd")) + goto out; + + prog = bpf_object__find_program_by_name(tgt_obj, "entry"); + if (!ASSERT_OK_PTR(prog, "find entry prog")) + goto out; + + main_fd = bpf_program__fd(prog); + if (!ASSERT_FALSE(main_fd < 0, "find entry prog fd")) + goto out; + + prog = bpf_object__find_program_by_name(tgt_obj, "callee"); + if (!ASSERT_OK_PTR(prog, "find callee prog")) + goto out; + + callee_fd = bpf_program__fd(prog); + if (!ASSERT_FALSE(callee_fd < 0, "find callee prog fd")) + goto out; + + i = 0; + err = bpf_map_update_elem(map_fd, &i, &callee_fd, BPF_ANY); + if (!ASSERT_OK(err, "update jmp_table")) + goto out; + + fentry_obj = bpf_object__open_file("tailcall_fentry_probe.bpf.o", NULL); + if (!ASSERT_OK_PTR(fentry_obj, "open fentry_obj file")) + goto out; + + prog = bpf_object__find_program_by_name(fentry_obj, "fentry_callee"); + if (!ASSERT_OK_PTR(prog, "find fentry prog")) + goto out; + + err = bpf_program__set_attach_target(prog, callee_fd, "callee"); + if (!ASSERT_OK(err, "set_attach_target callee")) + goto out; + + err = bpf_object__load(fentry_obj); + if (!ASSERT_OK(err, "load fentry_obj")) + goto out; + + fentry_link = bpf_program__attach_trace(prog); + if (!ASSERT_OK_PTR(fentry_link, "attach_trace")) + goto out; + + data_map = bpf_object__find_map_by_name(fentry_obj, ".bss"); + if (!ASSERT_FALSE(!data_map || !bpf_map__is_internal(data_map), + "find tailcall_fentry_probe.bss map")) + goto out; + + data_fd = bpf_map__fd(data_map); + if (!ASSERT_FALSE(data_fd < 0, + "find tailcall_fentry_probe.bss map fd")) + goto out; + + err = bpf_prog_test_run_opts(callee_fd, &topts); + ASSERT_OK(err, "direct callee"); + ASSERT_EQ(topts.retval, 7, "direct callee retval"); + + i = 0; + err = bpf_map_lookup_elem(data_fd, &i, &val); + ASSERT_OK(err, "direct fentry count"); + ASSERT_EQ(val, 1, "direct fentry count"); + + val = 0; + err = bpf_map_update_elem(data_fd, &i, &val, BPF_ANY); + ASSERT_OK(err, "reset fentry count"); + + err = bpf_prog_test_run_opts(main_fd, &topts); + ASSERT_OK(err, "tailcall"); + ASSERT_EQ(topts.retval, 7, "tailcall retval"); + + i = 0; + err = bpf_map_lookup_elem(data_fd, &i, &val); + ASSERT_OK(err, "fentry count"); + ASSERT_EQ(val, 1, "fentry count"); + +out: + bpf_link__destroy(fentry_link); + bpf_object__close(fentry_obj); + bpf_object__close(tgt_obj); +} + #define JMP_TABLE "/sys/fs/bpf/jmp_table" static int poke_thread_exit; @@ -1759,6 +1867,8 @@ void test_tailcalls(void) test_tailcall_bpf2bpf_fentry_fexit(); if (test__start_subtest("tailcall_bpf2bpf_fentry_entry")) test_tailcall_bpf2bpf_fentry_entry(); + if (test__start_subtest("tailcall_fentry_tailcallee")) + test_tailcall_fentry_tailcallee(); if (test__start_subtest("tailcall_poke")) test_tailcall_poke(); if (test__start_subtest("tailcall_bpf2bpf_hierarchy_1")) diff --git a/tools/testing/selftests/bpf/progs/tailcall_fentry_probe.c b/tools/testing/selftests/bpf/progs/tailcall_fentry_probe.c new file mode 100644 index 000000000000..b784aeffb316 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/tailcall_fentry_probe.c @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "vmlinux.h" + +#include <bpf/bpf_helpers.h> +#include <bpf/bpf_tracing.h> + +int count = 0; + +SEC("fentry/callee") +int BPF_PROG(fentry_callee, struct sk_buff *skb) +{ + count++; + return 0; +} + +char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/tailcall_fentry_target.c b/tools/testing/selftests/bpf/progs/tailcall_fentry_target.c new file mode 100644 index 000000000000..46da06ce323c --- /dev/null +++ b/tools/testing/selftests/bpf/progs/tailcall_fentry_target.c @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/bpf.h> + +#include <bpf/bpf_helpers.h> +#include "bpf_legacy.h" + +struct { + __uint(type, BPF_MAP_TYPE_PROG_ARRAY); + __uint(max_entries, 1); + __uint(key_size, sizeof(__u32)); + __uint(value_size, sizeof(__u32)); +} jmp_table SEC(".maps"); + +SEC("tc") +int callee(struct __sk_buff *skb) +{ + return 7; +} + +SEC("tc") +int entry(struct __sk_buff *skb) +{ + bpf_tail_call_static(skb, &jmp_table, 0); + return 0; +} + +char __license[] SEC("license") = "GPL"; -- 2.43.0 ^ permalink raw reply related [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-03-31 5:05 UTC | newest] Thread overview: 15+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-03-27 14:16 [PATCH bpf-next 0/2] bpf: enable x86 fentry on tail-called programs Takeru Hayasaka 2026-03-27 14:16 ` [PATCH bpf-next 1/2] bpf, x86: patch tail-call fentry slot on non-IBT JITs Takeru Hayasaka 2026-03-27 14:24 ` Alexei Starovoitov 2026-03-27 15:12 ` Takeru Hayasaka 2026-03-27 15:21 ` Alexei Starovoitov 2026-03-27 15:44 ` Takeru Hayasaka 2026-03-27 15:58 ` Alexei Starovoitov 2026-03-27 16:06 ` Takeru Hayasaka 2026-03-27 16:09 ` Alexei Starovoitov 2026-03-27 16:30 ` Takeru Hayasaka 2026-03-30 9:07 ` Leon Hwang 2026-03-30 16:46 ` Takeru Hayasaka 2026-03-31 2:24 ` Leon Hwang 2026-03-31 4:53 ` Takeru Hayasaka 2026-03-27 14:16 ` [PATCH bpf-next 2/2] selftests/bpf: cover fentry on tailcalled programs Takeru Hayasaka
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox