Re: [RFC PATCH 4/9] arm64/kprobes: Make the single-step machinery noinstr

Hongyan Xia <[email protected]>
Newsgroups org.infradead.lists.linux-arm-kernel,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
On 7/31/2026 11:38 PM, Mark Rutland wrote:
> On Mon, Jul 27, 2026 at 12:25:39PM +0000, Hongyan Xia wrote:
>> From: Hongyan Xia <[email protected]>
>>
>> Convert the core of the arm64 kprobe single-step flow to noinstr:
>> save_previous_kprobe(), restore_previous_kprobe(), set_current_kprobe(),
>> kprobes_save_local_irqflag(), kprobes_restore_local_irqflag(),
>> setup_singlestep(), reenter_kprobe() and post_kprobe_handler().
>>
>> These functions only touch per-cpu state, pt_regs and DAIF, except for:
>>
>> - kprobes_inc_nmissed_count(), which is generic and instrumentable; add
>>    an arm64-local wrapper that calls it bounded by
>>    instrumentation_begin()/end();
> 
> Why is it safe to call kprobes_inc_nmissed_count() if it can be
> instrumented?

There is list_for_each_entry_rcu() in that function. With RCU debugging 
or proving configs on, messy things like lockdep get in the way. When I 
looked at this issue before, my conclusion was it was not possible to 
make the kprobes_inc_nmissed_count() path noinstr.

> 
> How does that work for other architectures?

I think (other maintainers please correct me if I'm very wrong on this) 
arm64 is the first arch to actually attempt Kprobe noinstr. For example, 
x86 does Kprobe in a big int3 instrumentation window, so they genuinely 
need KPROBE_SS re-entry support and Kprobe page fault check, because 
instrumentation can happen. This series is in the other direction: 
Instead of patching all the possibilities each time we find one, make 
Kprobe noinstr to avoid the whole complexity.

>> - the instruction simulation path in setup_singlestep(), whose decode
>>    handlers are instrumentable; bound arch_simulate_insn() with an
>>    instrumentation window;
> 
> I don't think that's sufficient. If we can instrument that code, then we
> can have unbounded recursion, unless I'm missing something?
> 
> I think we need to fix that code to be noinstr safe.
> 
> Ada Cc'd was looking into making the insn code generally noinstr-safe,
> but that's a big job. Maybe it's possible to clean up the subset that's
> necessary for arch_simulate_insn()?

That would be fantastic. Yes, I believe this is the correct way to do 
noinstr here.

I'll investigate what that subset could be.

>> - the unrecoverable-reentry pr_warn()/dump_kprobe()/BUG() path and the
>>    default WARN_ON(), both bounded with instrumentation windows.
> 
> That sounds fine.
> 
> Mark.
> 
>> The __kprobes attribute (notrace + .kprobes.text) is replaced by
>> noinstr, which is a strict superset for these functions.
>>
>> Signed-off-by: Hongyan Xia <[email protected]>
>> ---
>>   arch/arm64/kernel/probes/kprobes.c | 39 ++++++++++++++++++------------
>>   1 file changed, 24 insertions(+), 15 deletions(-)
>>
>> diff --git a/arch/arm64/kernel/probes/kprobes.c b/arch/arm64/kernel/probes/kprobes.c
>> index 4e0efad5caf2..1b12341b2af3 100644
>> --- a/arch/arm64/kernel/probes/kprobes.c
>> +++ b/arch/arm64/kernel/probes/kprobes.c
>> @@ -14,6 +14,7 @@
>>   #include <linux/extable.h>
>>   #include <linux/kasan.h>
>>   #include <linux/kernel.h>
>> +#include <linux/instrumentation.h>
>>   #include <linux/kprobes.h>
>>   #include <linux/sched/debug.h>
>>   #include <linux/set_memory.h>
>> @@ -39,7 +40,7 @@
>>   DEFINE_PER_CPU(struct kprobe *, current_kprobe) = NULL;
>>   DEFINE_PER_CPU(struct kprobe_ctlblk, kprobe_ctlblk);
>>
>> -static void __kprobes
>> +static void noinstr
>>   post_kprobe_handler(struct kprobe *, struct kprobe_ctlblk *, struct pt_regs *);
>>
>>   void *alloc_insn_page(void)
>> @@ -170,7 +171,7 @@ void __kprobes arch_remove_kprobe(struct kprobe *p)
>>        }
>>   }
>>
>> -static void __kprobes save_previous_kprobe(struct kprobe_ctlblk *kcb)
>> +static void noinstr save_previous_kprobe(struct kprobe_ctlblk *kcb)
>>   {
>>        kcb->prev_kprobe.kp = kprobe_running();
>>        kcb->prev_kprobe.status = kcb->kprobe_status;
>> @@ -184,7 +185,7 @@ static void __kprobes save_previous_kprobe(struct kprobe_ctlblk *kcb)
>>        kcb->prev_kprobe.saved_irqflag = kcb->saved_irqflag;
>>   }
>>
>> -static void __kprobes restore_previous_kprobe(struct kprobe_ctlblk *kcb)
>> +static void noinstr restore_previous_kprobe(struct kprobe_ctlblk *kcb)
>>   {
>>        __this_cpu_write(current_kprobe, kcb->prev_kprobe.kp);
>>        kcb->kprobe_status = kcb->prev_kprobe.status;
>> @@ -197,7 +198,7 @@ static void __kprobes restore_previous_kprobe(struct kprobe_ctlblk *kcb)
>>        kcb->saved_irqflag = kcb->prev_kprobe.saved_irqflag;
>>   }
>>
>> -static void __kprobes set_current_kprobe(struct kprobe *p)
>> +static void noinstr set_current_kprobe(struct kprobe *p)
>>   {
>>        __this_cpu_write(current_kprobe, p);
>>   }
>> @@ -207,23 +208,23 @@ static void __kprobes set_current_kprobe(struct kprobe *p)
>>    * simple and avoid nesting exceptions. Interrupts do have to be disabled since
>>    * the kprobe state is per-CPU and doesn't get migrated.
>>    */
>> -static void __kprobes kprobes_save_local_irqflag(struct kprobe_ctlblk *kcb,
>> -                                             struct pt_regs *regs)
>> +static void noinstr kprobes_save_local_irqflag(struct kprobe_ctlblk *kcb,
>> +                                            struct pt_regs *regs)
>>   {
>>        kcb->saved_irqflag = regs->pstate & DAIF_MASK;
>>        regs->pstate |= DAIF_MASK;
>>   }
>>
>> -static void __kprobes kprobes_restore_local_irqflag(struct kprobe_ctlblk *kcb,
>> -                                             struct pt_regs *regs)
>> +static void noinstr kprobes_restore_local_irqflag(struct kprobe_ctlblk *kcb,
>> +                                               struct pt_regs *regs)
>>   {
>>        regs->pstate &= ~DAIF_MASK;
>>        regs->pstate |= kcb->saved_irqflag;
>>   }
>>
>> -static void __kprobes setup_singlestep(struct kprobe *p,
>> -                                    struct pt_regs *regs,
>> -                                    struct kprobe_ctlblk *kcb, int reenter)
>> +static void noinstr setup_singlestep(struct kprobe *p,
>> +                                  struct pt_regs *regs,
>> +                                  struct kprobe_ctlblk *kcb, int reenter)
>>   {
>>        unsigned long slot;
>>
>> @@ -244,13 +245,15 @@ static void __kprobes setup_singlestep(struct kprobe *p,
>>                instruction_pointer_set(regs, slot);
>>        } else {
>>                /* insn simulation */
>> +             instrumentation_begin();
>>                arch_simulate_insn(p, regs);
>> +             instrumentation_end();
>>        }
>>   }
>>
>> -static int __kprobes reenter_kprobe(struct kprobe *p,
>> -                                 struct pt_regs *regs,
>> -                                 struct kprobe_ctlblk *kcb)
>> +static int noinstr reenter_kprobe(struct kprobe *p,
>> +                               struct pt_regs *regs,
>> +                               struct kprobe_ctlblk *kcb)
>>   {
>>        switch (kcb->kprobe_status) {
>>        case KPROBE_HIT_SSDONE:
>> @@ -262,23 +265,29 @@ static int __kprobes reenter_kprobe(struct kprobe *p,
>>                 * recoverable one-level reentry, so handle it in the same way as
>>                 * reentry from KPROBE_HIT_ACTIVE or KPROBE_HIT_SSDONE.
>>                 */
>> +             instrumentation_begin();
>>                kprobes_inc_nmissed_count(p);
>> +             instrumentation_end();
>>                setup_singlestep(p, regs, kcb, 1);
>>                break;
>>        case KPROBE_REENTER:
>> +             instrumentation_begin();
>>                pr_warn("Failed to recover from reentered kprobes.\n");
>>                dump_kprobe(p);
>>                BUG();
>> +             instrumentation_end();
>>                break;
>>        default:
>> +             instrumentation_begin();
>>                WARN_ON(1);
>> +             instrumentation_end();
>>                return 0;
>>        }
>>
>>        return 1;
>>   }
>>
>> -static void __kprobes
>> +static void noinstr
>>   post_kprobe_handler(struct kprobe *cur, struct kprobe_ctlblk *kcb, struct pt_regs *regs)
>>   {
>>        /* return addr restore if non-branching insn */
>> --
>> 2.47.3
>>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.