[PATCH v3 04/13] arm64: debug: call step handlers statically

Anshuman Khandual anshuman.khandual at arm.com
Fri Jun 13 00:47:07 PDT 2025


On 09/06/25 11:04 PM, Ada Couprie Diaz wrote:
> Software stepping checks for the correct handler by iterating over a list
> of dynamically registered handlers and calling all of them until one
> handles the exception.
> 
> This is the only generic way to handle software stepping handlers in arm64
> as the exception does not provide an immediate that could be checked,
> contrary to software breakpoints.
> 
> However, the registration mechanism is not exported and has only
> two current users : the KGDB stepping handler, and the uprobe single step
> handler.
> Given that one comes from user mode and the other from kernel mode, call
> the appropriate one by checking the source EL of the exception.
> Add a stand-in that returns DBG_HOOK_ERROR when the configuration
> options are not enabled.
> 
> Unify the naming of the handler to XXX_singlestep_handler(), making it
> clear they are related.

Makes sense.

> 
> Signed-off-by: Ada Couprie Diaz <ada.coupriediaz at arm.com>
> ---
>  arch/arm64/include/asm/kgdb.h      |  9 +++++++++
>  arch/arm64/include/asm/uprobes.h   |  9 +++++++++
>  arch/arm64/kernel/debug-monitors.c | 25 ++++++-------------------
>  arch/arm64/kernel/kgdb.c           | 17 +++--------------
>  arch/arm64/kernel/probes/uprobes.c |  9 +--------
>  5 files changed, 28 insertions(+), 41 deletions(-)
> 
> diff --git a/arch/arm64/include/asm/kgdb.h b/arch/arm64/include/asm/kgdb.h
> index 82a76b2102fb..fa0c1edb8a65 100644
> --- a/arch/arm64/include/asm/kgdb.h
> +++ b/arch/arm64/include/asm/kgdb.h
> @@ -26,6 +26,15 @@ extern int kgdb_fault_expected;
>  
>  int kgdb_brk_handler(struct pt_regs *regs, unsigned long esr);
>  int kgdb_compiled_brk_handler(struct pt_regs *regs, unsigned long esr);
> +#ifdef CONFIG_KGDB
> +int kgdb_singlestep_handler(struct pt_regs *regs, unsigned long esr);
> +#else
> +static inline int kgdb_singlestep_handler(struct pt_regs *regs,
> +	unsigned long esr)
> +{
> +	return DBG_HOOK_ERROR;
> +}
> +#endif
>  
>  #endif /* !__ASSEMBLY__ */
>  
> diff --git a/arch/arm64/include/asm/uprobes.h b/arch/arm64/include/asm/uprobes.h
> index 3659a79a9f32..5858e436a5a7 100644
> --- a/arch/arm64/include/asm/uprobes.h
> +++ b/arch/arm64/include/asm/uprobes.h
> @@ -29,5 +29,14 @@ struct arch_uprobe {
>  };
>  
>  int uprobe_brk_handler(struct pt_regs *regs, unsigned long esr);
> +#ifdef CONFIG_UPROBES
> +int uprobe_singlestep_handler(struct pt_regs *regs, unsigned long esr);
> +#else
> +static inline int uprobe_singlestep_handler(struct pt_regs *regs,
> +	unsigned long esr)
> +{
> +	return DBG_HOOK_ERROR;
> +}
> +#endif
>  
>  #endif
> diff --git a/arch/arm64/kernel/debug-monitors.c b/arch/arm64/kernel/debug-monitors.c
> index 15d7158a7548..b156bef7f61e 100644
> --- a/arch/arm64/kernel/debug-monitors.c
> +++ b/arch/arm64/kernel/debug-monitors.c
> @@ -200,30 +200,17 @@ void unregister_kernel_step_hook(struct step_hook *hook)
>  }
>  
>  /*
> - * Call registered single step handlers
> + * Call single step handlers
>   * There is no Syndrome info to check for determining the handler.
> - * So we call all the registered handlers, until the right handler is
> - * found which returns zero.
> + * However, there is only one possible handler for user and kernel modes, so
> + * check and call the appropriate one.
>   */
>  static int call_step_hook(struct pt_regs *regs, unsigned long esr)
>  {
> -	struct step_hook *hook;
> -	struct list_head *list;
> -	int retval = DBG_HOOK_ERROR;
> +	if (user_mode(regs))
> +		return uprobe_singlestep_handler(regs, esr);
>  
> -	list = user_mode(regs) ? &user_step_hook : &kernel_step_hook;
> -
> -	/*
> -	 * Since single-step exception disables interrupt, this function is
> -	 * entirely not preemptible, and we can use rcu list safely here.
> -	 */
> -	list_for_each_entry_rcu(hook, list, node)	{
> -		retval = hook->fn(regs, esr);
> -		if (retval == DBG_HOOK_HANDLED)
> -			break;
> -	}
> -
> -	return retval;
> +	return kgdb_singlestep_handler(regs, esr);
>  }
>  NOKPROBE_SYMBOL(call_step_hook);
>  
> diff --git a/arch/arm64/kernel/kgdb.c b/arch/arm64/kernel/kgdb.c
> index b5a3b9c85a65..8f6ce2ea005c 100644
> --- a/arch/arm64/kernel/kgdb.c
> +++ b/arch/arm64/kernel/kgdb.c
> @@ -250,7 +250,7 @@ int kgdb_compiled_brk_handler(struct pt_regs *regs, unsigned long esr)
>  }
>  NOKPROBE_SYMBOL(kgdb_compiled_brk_handler);
>  
> -static int kgdb_step_brk_fn(struct pt_regs *regs, unsigned long esr)
> +int kgdb_singlestep_handler(struct pt_regs *regs, unsigned long esr)

This rename makes sense but as mentioned later kgdb_single_step_handler()
might save some changes in uprobes callback function.

>  {
>  	if (!kgdb_single_step)
>  		return DBG_HOOK_ERROR;
> @@ -258,11 +258,7 @@ static int kgdb_step_brk_fn(struct pt_regs *regs, unsigned long esr)
>  	kgdb_handle_exception(0, SIGTRAP, 0, regs);
>  	return DBG_HOOK_HANDLED;
>  }
> -NOKPROBE_SYMBOL(kgdb_step_brk_fn);
> -
> -static struct step_hook kgdb_step_hook = {
> -	.fn		= kgdb_step_brk_fn
> -};
> +NOKPROBE_SYMBOL(kgdb_singlestep_handler);
>  
>  static int __kgdb_notify(struct die_args *args, unsigned long cmd)
>  {
> @@ -301,13 +297,7 @@ static struct notifier_block kgdb_notifier = {
>   */
>  int kgdb_arch_init(void)
>  {
> -	int ret = register_die_notifier(&kgdb_notifier);
> -
> -	if (ret != 0)
> -		return ret;
> -
> -	register_kernel_step_hook(&kgdb_step_hook);
> -	return 0;
> +	return register_die_notifier(&kgdb_notifier);
>  }
>  
>  /*
> @@ -317,7 +307,6 @@ int kgdb_arch_init(void)
>   */
>  void kgdb_arch_exit(void)
>  {
> -	unregister_kernel_step_hook(&kgdb_step_hook);
>  	unregister_die_notifier(&kgdb_notifier);
>  }

kgdb_arch_init()/_exit() now deals only with kgdb_notifier registration
and un-registration only.
 >  
> diff --git a/arch/arm64/kernel/probes/uprobes.c b/arch/arm64/kernel/probes/uprobes.c
> index ad68b4a5974d..fefc990860bc 100644
> --- a/arch/arm64/kernel/probes/uprobes.c
> +++ b/arch/arm64/kernel/probes/uprobes.c
> @@ -182,7 +182,7 @@ int uprobe_brk_handler(struct pt_regs *regs,
>  	return DBG_HOOK_ERROR;
>  }
>  
> -static int uprobe_single_step_handler(struct pt_regs *regs,
> +int uprobe_singlestep_handler(struct pt_regs *regs,
>  				      unsigned long esr)

A small nit - if the kgdb handler be changed as kgdb_single_step_handler()
the above rename can be skipped.
 >  {
>  	struct uprobe_task *utask = current->utask;
> @@ -194,15 +194,8 @@ static int uprobe_single_step_handler(struct pt_regs *regs,
>  	return DBG_HOOK_ERROR;
>  }
>  
> -/* uprobe single step handler hook */
> -static struct step_hook uprobes_step_hook = {
> -	.fn = uprobe_single_step_handler,
> -};
> -
>  static int __init arch_init_uprobes(void)
>  {
> -	register_user_step_hook(&uprobes_step_hook);
> -
>  	return 0;
>  }
>  

The arch hook arch_init_uprobes() is redundant now and can be dropped.

static int __init arch_init_uprobes(void)
{
        return 0;
}
device_initcall(arch_init_uprobes);

git grep arch_init_uprobes
arch/arm64/kernel/probes/uprobes.c:static int __init arch_init_uprobes(void)
arch/arm64/kernel/probes/uprobes.c:device_initcall(arch_init_uprobes);

Otherwise LGTM.



More information about the linux-arm-kernel mailing list