diff mbox series

[v2] riscv: stacktrace: Add USER_STACKTRACE support

Message ID 20240604034729.841930-1-ruanjinjie@huawei.com (mailing list archive)
State Superseded
Headers show
Series [v2] riscv: stacktrace: Add USER_STACKTRACE support | expand

Checks

Context Check Description
conchuod/vmtest-for-next-PR success PR summary
conchuod/patch-1-test-1 success .github/scripts/patches/tests/build_rv32_defconfig.sh
conchuod/patch-1-test-2 success .github/scripts/patches/tests/build_rv64_clang_allmodconfig.sh
conchuod/patch-1-test-3 success .github/scripts/patches/tests/build_rv64_gcc_allmodconfig.sh
conchuod/patch-1-test-4 success .github/scripts/patches/tests/build_rv64_nommu_k210_defconfig.sh
conchuod/patch-1-test-5 success .github/scripts/patches/tests/build_rv64_nommu_virt_defconfig.sh
conchuod/patch-1-test-6 success .github/scripts/patches/tests/checkpatch.sh
conchuod/patch-1-test-7 success .github/scripts/patches/tests/dtb_warn_rv64.sh
conchuod/patch-1-test-8 success .github/scripts/patches/tests/header_inline.sh
conchuod/patch-1-test-9 success .github/scripts/patches/tests/kdoc.sh
conchuod/patch-1-test-10 success .github/scripts/patches/tests/module_param.sh
conchuod/patch-1-test-11 success .github/scripts/patches/tests/verify_fixes.sh
conchuod/patch-1-test-12 success .github/scripts/patches/tests/verify_signedoff.sh

Commit Message

Jinjie Ruan June 4, 2024, 3:47 a.m. UTC
Currently, userstacktrace is unsupported for riscv. So use the
perf_callchain_user() code as blueprint to implement the
arch_stack_walk_user() which add userstacktrace support on riscv.
Meanwhile, we can use arch_stack_walk_user() to simplify the implementation
of perf_callchain_user().

Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
Reported-by: kernel test robot <lkp@intel.com>
Closes: https://lore.kernel.org/oe-kbuild-all/202406012109.PDAjXm2i-lkp@intel.com/
---
v2:
- Fix the cocci warning, !A || A && B is equivalent to !A || B.
---
 arch/riscv/Kconfig                 |  1 +
 arch/riscv/kernel/perf_callchain.c | 46 ++----------------------------
 arch/riscv/kernel/stacktrace.c     | 43 ++++++++++++++++++++++++++++
 3 files changed, 47 insertions(+), 43 deletions(-)

Comments

Jinjie Ruan June 17, 2024, 1:36 a.m. UTC | #1
Gentle ping.

On 2024/6/4 11:47, Jinjie Ruan wrote:
> Currently, userstacktrace is unsupported for riscv. So use the
> perf_callchain_user() code as blueprint to implement the
> arch_stack_walk_user() which add userstacktrace support on riscv.
> Meanwhile, we can use arch_stack_walk_user() to simplify the implementation
> of perf_callchain_user().
> 
> Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
> Reported-by: kernel test robot <lkp@intel.com>
> Closes: https://lore.kernel.org/oe-kbuild-all/202406012109.PDAjXm2i-lkp@intel.com/
> ---
> v2:
> - Fix the cocci warning, !A || A && B is equivalent to !A || B.
> ---
>  arch/riscv/Kconfig                 |  1 +
>  arch/riscv/kernel/perf_callchain.c | 46 ++----------------------------
>  arch/riscv/kernel/stacktrace.c     | 43 ++++++++++++++++++++++++++++
>  3 files changed, 47 insertions(+), 43 deletions(-)
> 
> diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig
> index 0525ee2d63c7..6ed96d935b8f 100644
> --- a/arch/riscv/Kconfig
> +++ b/arch/riscv/Kconfig
> @@ -193,6 +193,7 @@ config RISCV
>  	select THREAD_INFO_IN_TASK
>  	select TRACE_IRQFLAGS_SUPPORT
>  	select UACCESS_MEMCPY if !MMU
> +	select USER_STACKTRACE_SUPPORT
>  	select ZONE_DMA32 if 64BIT
>  
>  config CLANG_SUPPORTS_DYNAMIC_FTRACE
> diff --git a/arch/riscv/kernel/perf_callchain.c b/arch/riscv/kernel/perf_callchain.c
> index 3348a61de7d9..c7468af77c66 100644
> --- a/arch/riscv/kernel/perf_callchain.c
> +++ b/arch/riscv/kernel/perf_callchain.c
> @@ -6,37 +6,9 @@
>  
>  #include <asm/stacktrace.h>
>  
> -/*
> - * Get the return address for a single stackframe and return a pointer to the
> - * next frame tail.
> - */
> -static unsigned long user_backtrace(struct perf_callchain_entry_ctx *entry,
> -				    unsigned long fp, unsigned long reg_ra)
> +static bool fill_callchain(void *entry, unsigned long pc)
>  {
> -	struct stackframe buftail;
> -	unsigned long ra = 0;
> -	unsigned long __user *user_frame_tail =
> -		(unsigned long __user *)(fp - sizeof(struct stackframe));
> -
> -	/* Check accessibility of one struct frame_tail beyond */
> -	if (!access_ok(user_frame_tail, sizeof(buftail)))
> -		return 0;
> -	if (__copy_from_user_inatomic(&buftail, user_frame_tail,
> -				      sizeof(buftail)))
> -		return 0;
> -
> -	if (reg_ra != 0)
> -		ra = reg_ra;
> -	else
> -		ra = buftail.ra;
> -
> -	fp = buftail.fp;
> -	if (ra != 0)
> -		perf_callchain_store(entry, ra);
> -	else
> -		return 0;
> -
> -	return fp;
> +	return perf_callchain_store(entry, pc) == 0;
>  }
>  
>  /*
> @@ -56,19 +28,7 @@ static unsigned long user_backtrace(struct perf_callchain_entry_ctx *entry,
>  void perf_callchain_user(struct perf_callchain_entry_ctx *entry,
>  			 struct pt_regs *regs)
>  {
> -	unsigned long fp = 0;
> -
> -	fp = regs->s0;
> -	perf_callchain_store(entry, regs->epc);
> -
> -	fp = user_backtrace(entry, fp, regs->ra);
> -	while (fp && !(fp & 0x3) && entry->nr < entry->max_stack)
> -		fp = user_backtrace(entry, fp, 0);
> -}
> -
> -static bool fill_callchain(void *entry, unsigned long pc)
> -{
> -	return perf_callchain_store(entry, pc) == 0;
> +	arch_stack_walk_user(fill_callchain, entry, regs);
>  }
>  
>  void perf_callchain_kernel(struct perf_callchain_entry_ctx *entry,
> diff --git a/arch/riscv/kernel/stacktrace.c b/arch/riscv/kernel/stacktrace.c
> index 528ec7cc9a62..9685a2baa5d9 100644
> --- a/arch/riscv/kernel/stacktrace.c
> +++ b/arch/riscv/kernel/stacktrace.c
> @@ -161,3 +161,46 @@ noinline void arch_stack_walk(stack_trace_consume_fn consume_entry, void *cookie
>  {
>  	walk_stackframe(task, regs, consume_entry, cookie);
>  }
> +
> +/*
> + * Get the return address for a single stackframe and return a pointer to the
> + * next frame tail.
> + */
> +static unsigned long unwind_user_frame(stack_trace_consume_fn consume_entry,
> +				       void *cookie, unsigned long fp,
> +				       unsigned long reg_ra)
> +{
> +	struct stackframe buftail;
> +	unsigned long ra = 0;
> +	unsigned long __user *user_frame_tail =
> +		(unsigned long __user *)(fp - sizeof(struct stackframe));
> +
> +	/* Check accessibility of one struct frame_tail beyond */
> +	if (!access_ok(user_frame_tail, sizeof(buftail)))
> +		return 0;
> +	if (__copy_from_user_inatomic(&buftail, user_frame_tail,
> +				      sizeof(buftail)))
> +		return 0;
> +
> +	ra = reg_ra ? : buftail.ra;
> +
> +	fp = buftail.fp;
> +	if (!ra || !consume_entry(cookie, ra))
> +		return 0;
> +
> +	return fp;
> +}
> +
> +void arch_stack_walk_user(stack_trace_consume_fn consume_entry, void *cookie,
> +			  const struct pt_regs *regs)
> +{
> +	unsigned long fp = 0;
> +
> +	fp = regs->s0;
> +	if (!consume_entry(cookie, regs->epc))
> +		return;
> +
> +	fp = unwind_user_frame(consume_entry, cookie, fp, regs->ra);
> +	while (fp && !(fp & 0x3))
> +		fp = unwind_user_frame(consume_entry, cookie, fp, 0);
> +}
Björn Töpel July 3, 2024, 2:36 p.m. UTC | #2
Jinjie Ruan <ruanjinjie@huawei.com> writes:

> Currently, userstacktrace is unsupported for riscv. So use the
> perf_callchain_user() code as blueprint to implement the
> arch_stack_walk_user() which add userstacktrace support on riscv.
> Meanwhile, we can use arch_stack_walk_user() to simplify the implementation
> of perf_callchain_user().
>
> Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
> Reported-by: kernel test robot <lkp@intel.com>
> Closes: https://lore.kernel.org/oe-kbuild-all/202406012109.PDAjXm2i-lkp@intel.com/

For future reference: The LTP tags shouldn't be used when you're
spinning a new version.

> ---
> v2:
> - Fix the cocci warning, !A || A && B is equivalent to !A || B.
> ---
>  arch/riscv/Kconfig                 |  1 +
>  arch/riscv/kernel/perf_callchain.c | 46 ++----------------------------
>  arch/riscv/kernel/stacktrace.c     | 43 ++++++++++++++++++++++++++++
>  3 files changed, 47 insertions(+), 43 deletions(-)

[...]

> diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig
> index 0525ee2d63c7..6ed96d935b8f 100644
> --- a/arch/riscv/Kconfig
> +++ b/arch/riscv/Kconfig
> @@ -193,6 +193,7 @@ config RISCV
>  	select THREAD_INFO_IN_TASK
>  	select TRACE_IRQFLAGS_SUPPORT
>  	select UACCESS_MEMCPY if !MMU
> +	select USER_STACKTRACE_SUPPORT
>  	select ZONE_DMA32 if 64BIT
>  
>  config CLANG_SUPPORTS_DYNAMIC_FTRACE
> diff --git a/arch/riscv/kernel/perf_callchain.c b/arch/riscv/kernel/perf_callchain.c
> index 3348a61de7d9..c7468af77c66 100644
> --- a/arch/riscv/kernel/perf_callchain.c
> +++ b/arch/riscv/kernel/perf_callchain.c
> @@ -6,37 +6,9 @@
>  
>  #include <asm/stacktrace.h>
>  
> -/*
> - * Get the return address for a single stackframe and return a pointer to the
> - * next frame tail.
> - */
> -static unsigned long user_backtrace(struct perf_callchain_entry_ctx *entry,
> -				    unsigned long fp, unsigned long reg_ra)
> +static bool fill_callchain(void *entry, unsigned long pc)
>  {
> -	struct stackframe buftail;
> -	unsigned long ra = 0;
> -	unsigned long __user *user_frame_tail =
> -		(unsigned long __user *)(fp - sizeof(struct stackframe));
> -
> -	/* Check accessibility of one struct frame_tail beyond */
> -	if (!access_ok(user_frame_tail, sizeof(buftail)))
> -		return 0;
> -	if (__copy_from_user_inatomic(&buftail, user_frame_tail,
> -				      sizeof(buftail)))
> -		return 0;
> -
> -	if (reg_ra != 0)
> -		ra = reg_ra;
> -	else
> -		ra = buftail.ra;
> -
> -	fp = buftail.fp;
> -	if (ra != 0)
> -		perf_callchain_store(entry, ra);
> -	else
> -		return 0;
> -
> -	return fp;
> +	return perf_callchain_store(entry, pc) == 0;
>  }
>  
>  /*
> @@ -56,19 +28,7 @@ static unsigned long user_backtrace(struct perf_callchain_entry_ctx *entry,
>  void perf_callchain_user(struct perf_callchain_entry_ctx *entry,
>  			 struct pt_regs *regs)
>  {
> -	unsigned long fp = 0;
> -
> -	fp = regs->s0;
> -	perf_callchain_store(entry, regs->epc);
> -
> -	fp = user_backtrace(entry, fp, regs->ra);
> -	while (fp && !(fp & 0x3) && entry->nr < entry->max_stack)
> -		fp = user_backtrace(entry, fp, 0);
> -}
> -
> -static bool fill_callchain(void *entry, unsigned long pc)
> -{
> -	return perf_callchain_store(entry, pc) == 0;
> +	arch_stack_walk_user(fill_callchain, entry, regs);
>  }
>  
>  void perf_callchain_kernel(struct perf_callchain_entry_ctx *entry,
> diff --git a/arch/riscv/kernel/stacktrace.c b/arch/riscv/kernel/stacktrace.c
> index 528ec7cc9a62..9685a2baa5d9 100644
> --- a/arch/riscv/kernel/stacktrace.c
> +++ b/arch/riscv/kernel/stacktrace.c
> @@ -161,3 +161,46 @@ noinline void arch_stack_walk(stack_trace_consume_fn consume_entry, void *cookie
>  {
>  	walk_stackframe(task, regs, consume_entry, cookie);
>  }
> +
> +/*
> + * Get the return address for a single stackframe and return a pointer to the
> + * next frame tail.
> + */
> +static unsigned long unwind_user_frame(stack_trace_consume_fn consume_entry,
> +				       void *cookie, unsigned long fp,
> +				       unsigned long reg_ra)
> +{
> +	struct stackframe buftail;
> +	unsigned long ra = 0;
> +	unsigned long __user *user_frame_tail =
> +		(unsigned long __user *)(fp - sizeof(struct stackframe));
> +
> +	/* Check accessibility of one struct frame_tail beyond */
> +	if (!access_ok(user_frame_tail, sizeof(buftail)))
> +		return 0;
> +	if (__copy_from_user_inatomic(&buftail, user_frame_tail,
> +				      sizeof(buftail)))
> +		return 0;
> +
> +	ra = reg_ra ? : buftail.ra;
> +
> +	fp = buftail.fp;
> +	if (!ra || !consume_entry(cookie, ra))
> +		return 0;
> +
> +	return fp;
> +}
> +
> +void arch_stack_walk_user(stack_trace_consume_fn consume_entry, void *cookie,
> +			  const struct pt_regs *regs)
> +{
> +	unsigned long fp = 0;
> +
> +	fp = regs->s0;
> +	if (!consume_entry(cookie, regs->epc))
> +		return;
> +
> +	fp = unwind_user_frame(consume_entry, cookie, fp, regs->ra);
> +	while (fp && !(fp & 0x3))

Just an observation that the "entry->nr < entry->max_stack" check was
removed in this generalization, but that's OK since it's checked in
perf_callchain_store().

Not really part of your change, but shouldn't the check be 0x7 (checking
for 16B sp/fp alignment), rather than 0x3?


Björn
Jinjie Ruan July 4, 2024, 2:58 a.m. UTC | #3
On 2024/7/3 22:36, Björn Töpel wrote:
> Jinjie Ruan <ruanjinjie@huawei.com> writes:
> 
>> Currently, userstacktrace is unsupported for riscv. So use the
>> perf_callchain_user() code as blueprint to implement the
>> arch_stack_walk_user() which add userstacktrace support on riscv.
>> Meanwhile, we can use arch_stack_walk_user() to simplify the implementation
>> of perf_callchain_user().
>>
>> Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
>> Reported-by: kernel test robot <lkp@intel.com>
>> Closes: https://lore.kernel.org/oe-kbuild-all/202406012109.PDAjXm2i-lkp@intel.com/
> 
> For future reference: The LTP tags shouldn't be used when you're
> spinning a new version.

Thank you! I'll remove it.

> 
>> ---
>> v2:
>> - Fix the cocci warning, !A || A && B is equivalent to !A || B.
>> ---
>>  arch/riscv/Kconfig                 |  1 +
>>  arch/riscv/kernel/perf_callchain.c | 46 ++----------------------------
>>  arch/riscv/kernel/stacktrace.c     | 43 ++++++++++++++++++++++++++++
>>  3 files changed, 47 insertions(+), 43 deletions(-)
> 
> [...]
> 
>> diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig
>> index 0525ee2d63c7..6ed96d935b8f 100644
>> --- a/arch/riscv/Kconfig
>> +++ b/arch/riscv/Kconfig
>> @@ -193,6 +193,7 @@ config RISCV
>>  	select THREAD_INFO_IN_TASK
>>  	select TRACE_IRQFLAGS_SUPPORT
>>  	select UACCESS_MEMCPY if !MMU
>> +	select USER_STACKTRACE_SUPPORT
>>  	select ZONE_DMA32 if 64BIT
>>  
>>  config CLANG_SUPPORTS_DYNAMIC_FTRACE
>> diff --git a/arch/riscv/kernel/perf_callchain.c b/arch/riscv/kernel/perf_callchain.c
>> index 3348a61de7d9..c7468af77c66 100644
>> --- a/arch/riscv/kernel/perf_callchain.c
>> +++ b/arch/riscv/kernel/perf_callchain.c
>> @@ -6,37 +6,9 @@
>>  
>>  #include <asm/stacktrace.h>
>>  
>> -/*
>> - * Get the return address for a single stackframe and return a pointer to the
>> - * next frame tail.
>> - */
>> -static unsigned long user_backtrace(struct perf_callchain_entry_ctx *entry,
>> -				    unsigned long fp, unsigned long reg_ra)
>> +static bool fill_callchain(void *entry, unsigned long pc)
>>  {
>> -	struct stackframe buftail;
>> -	unsigned long ra = 0;
>> -	unsigned long __user *user_frame_tail =
>> -		(unsigned long __user *)(fp - sizeof(struct stackframe));
>> -
>> -	/* Check accessibility of one struct frame_tail beyond */
>> -	if (!access_ok(user_frame_tail, sizeof(buftail)))
>> -		return 0;
>> -	if (__copy_from_user_inatomic(&buftail, user_frame_tail,
>> -				      sizeof(buftail)))
>> -		return 0;
>> -
>> -	if (reg_ra != 0)
>> -		ra = reg_ra;
>> -	else
>> -		ra = buftail.ra;
>> -
>> -	fp = buftail.fp;
>> -	if (ra != 0)
>> -		perf_callchain_store(entry, ra);
>> -	else
>> -		return 0;
>> -
>> -	return fp;
>> +	return perf_callchain_store(entry, pc) == 0;
>>  }
>>  
>>  /*
>> @@ -56,19 +28,7 @@ static unsigned long user_backtrace(struct perf_callchain_entry_ctx *entry,
>>  void perf_callchain_user(struct perf_callchain_entry_ctx *entry,
>>  			 struct pt_regs *regs)
>>  {
>> -	unsigned long fp = 0;
>> -
>> -	fp = regs->s0;
>> -	perf_callchain_store(entry, regs->epc);
>> -
>> -	fp = user_backtrace(entry, fp, regs->ra);
>> -	while (fp && !(fp & 0x3) && entry->nr < entry->max_stack)
>> -		fp = user_backtrace(entry, fp, 0);
>> -}
>> -
>> -static bool fill_callchain(void *entry, unsigned long pc)
>> -{
>> -	return perf_callchain_store(entry, pc) == 0;
>> +	arch_stack_walk_user(fill_callchain, entry, regs);
>>  }
>>  
>>  void perf_callchain_kernel(struct perf_callchain_entry_ctx *entry,
>> diff --git a/arch/riscv/kernel/stacktrace.c b/arch/riscv/kernel/stacktrace.c
>> index 528ec7cc9a62..9685a2baa5d9 100644
>> --- a/arch/riscv/kernel/stacktrace.c
>> +++ b/arch/riscv/kernel/stacktrace.c
>> @@ -161,3 +161,46 @@ noinline void arch_stack_walk(stack_trace_consume_fn consume_entry, void *cookie
>>  {
>>  	walk_stackframe(task, regs, consume_entry, cookie);
>>  }
>> +
>> +/*
>> + * Get the return address for a single stackframe and return a pointer to the
>> + * next frame tail.
>> + */
>> +static unsigned long unwind_user_frame(stack_trace_consume_fn consume_entry,
>> +				       void *cookie, unsigned long fp,
>> +				       unsigned long reg_ra)
>> +{
>> +	struct stackframe buftail;
>> +	unsigned long ra = 0;
>> +	unsigned long __user *user_frame_tail =
>> +		(unsigned long __user *)(fp - sizeof(struct stackframe));
>> +
>> +	/* Check accessibility of one struct frame_tail beyond */
>> +	if (!access_ok(user_frame_tail, sizeof(buftail)))
>> +		return 0;
>> +	if (__copy_from_user_inatomic(&buftail, user_frame_tail,
>> +				      sizeof(buftail)))
>> +		return 0;
>> +
>> +	ra = reg_ra ? : buftail.ra;
>> +
>> +	fp = buftail.fp;
>> +	if (!ra || !consume_entry(cookie, ra))
>> +		return 0;
>> +
>> +	return fp;
>> +}
>> +
>> +void arch_stack_walk_user(stack_trace_consume_fn consume_entry, void *cookie,
>> +			  const struct pt_regs *regs)
>> +{
>> +	unsigned long fp = 0;
>> +
>> +	fp = regs->s0;
>> +	if (!consume_entry(cookie, regs->epc))
>> +		return;
>> +
>> +	fp = unwind_user_frame(consume_entry, cookie, fp, regs->ra);
>> +	while (fp && !(fp & 0x3))
> 
> Just an observation that the "entry->nr < entry->max_stack" check was
> removed in this generalization, but that's OK since it's checked in
> perf_callchain_store().

Yes, the checked is not necessary, because perf_callchain_store() check
"entry->nr < entry->max_stack"

Also the caller function stack_trace_save_user() registers the
consume_entry callback "stack_trace_consume_entry" for saving user stack
traces into a storage array, in which entry->nr against entry->max_stack
is put.

> 
> Not really part of your change, but shouldn't the check be 0x7 (checking
> for 16B sp/fp alignment), rather than 0x3?

You are right! From the below "RISCV Calling Convention" :

    "In the standard RISC-V calling convention, the stack grows downward

     and the stack pointer is always kept 16-byte aligned"

So the sp should be 16-byte aligned, and the "PT_SIZE_ON_STACK" is also
16-byte aligned, so I think the fp is also 16-byte aligned.

Link: https://riscv.org/wp-content/uploads/2015/01/riscv-calling.pdf
> 
> 
> Björn
> 
>
diff mbox series

Patch

diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig
index 0525ee2d63c7..6ed96d935b8f 100644
--- a/arch/riscv/Kconfig
+++ b/arch/riscv/Kconfig
@@ -193,6 +193,7 @@  config RISCV
 	select THREAD_INFO_IN_TASK
 	select TRACE_IRQFLAGS_SUPPORT
 	select UACCESS_MEMCPY if !MMU
+	select USER_STACKTRACE_SUPPORT
 	select ZONE_DMA32 if 64BIT
 
 config CLANG_SUPPORTS_DYNAMIC_FTRACE
diff --git a/arch/riscv/kernel/perf_callchain.c b/arch/riscv/kernel/perf_callchain.c
index 3348a61de7d9..c7468af77c66 100644
--- a/arch/riscv/kernel/perf_callchain.c
+++ b/arch/riscv/kernel/perf_callchain.c
@@ -6,37 +6,9 @@ 
 
 #include <asm/stacktrace.h>
 
-/*
- * Get the return address for a single stackframe and return a pointer to the
- * next frame tail.
- */
-static unsigned long user_backtrace(struct perf_callchain_entry_ctx *entry,
-				    unsigned long fp, unsigned long reg_ra)
+static bool fill_callchain(void *entry, unsigned long pc)
 {
-	struct stackframe buftail;
-	unsigned long ra = 0;
-	unsigned long __user *user_frame_tail =
-		(unsigned long __user *)(fp - sizeof(struct stackframe));
-
-	/* Check accessibility of one struct frame_tail beyond */
-	if (!access_ok(user_frame_tail, sizeof(buftail)))
-		return 0;
-	if (__copy_from_user_inatomic(&buftail, user_frame_tail,
-				      sizeof(buftail)))
-		return 0;
-
-	if (reg_ra != 0)
-		ra = reg_ra;
-	else
-		ra = buftail.ra;
-
-	fp = buftail.fp;
-	if (ra != 0)
-		perf_callchain_store(entry, ra);
-	else
-		return 0;
-
-	return fp;
+	return perf_callchain_store(entry, pc) == 0;
 }
 
 /*
@@ -56,19 +28,7 @@  static unsigned long user_backtrace(struct perf_callchain_entry_ctx *entry,
 void perf_callchain_user(struct perf_callchain_entry_ctx *entry,
 			 struct pt_regs *regs)
 {
-	unsigned long fp = 0;
-
-	fp = regs->s0;
-	perf_callchain_store(entry, regs->epc);
-
-	fp = user_backtrace(entry, fp, regs->ra);
-	while (fp && !(fp & 0x3) && entry->nr < entry->max_stack)
-		fp = user_backtrace(entry, fp, 0);
-}
-
-static bool fill_callchain(void *entry, unsigned long pc)
-{
-	return perf_callchain_store(entry, pc) == 0;
+	arch_stack_walk_user(fill_callchain, entry, regs);
 }
 
 void perf_callchain_kernel(struct perf_callchain_entry_ctx *entry,
diff --git a/arch/riscv/kernel/stacktrace.c b/arch/riscv/kernel/stacktrace.c
index 528ec7cc9a62..9685a2baa5d9 100644
--- a/arch/riscv/kernel/stacktrace.c
+++ b/arch/riscv/kernel/stacktrace.c
@@ -161,3 +161,46 @@  noinline void arch_stack_walk(stack_trace_consume_fn consume_entry, void *cookie
 {
 	walk_stackframe(task, regs, consume_entry, cookie);
 }
+
+/*
+ * Get the return address for a single stackframe and return a pointer to the
+ * next frame tail.
+ */
+static unsigned long unwind_user_frame(stack_trace_consume_fn consume_entry,
+				       void *cookie, unsigned long fp,
+				       unsigned long reg_ra)
+{
+	struct stackframe buftail;
+	unsigned long ra = 0;
+	unsigned long __user *user_frame_tail =
+		(unsigned long __user *)(fp - sizeof(struct stackframe));
+
+	/* Check accessibility of one struct frame_tail beyond */
+	if (!access_ok(user_frame_tail, sizeof(buftail)))
+		return 0;
+	if (__copy_from_user_inatomic(&buftail, user_frame_tail,
+				      sizeof(buftail)))
+		return 0;
+
+	ra = reg_ra ? : buftail.ra;
+
+	fp = buftail.fp;
+	if (!ra || !consume_entry(cookie, ra))
+		return 0;
+
+	return fp;
+}
+
+void arch_stack_walk_user(stack_trace_consume_fn consume_entry, void *cookie,
+			  const struct pt_regs *regs)
+{
+	unsigned long fp = 0;
+
+	fp = regs->s0;
+	if (!consume_entry(cookie, regs->epc))
+		return;
+
+	fp = unwind_user_frame(consume_entry, cookie, fp, regs->ra);
+	while (fp && !(fp & 0x3))
+		fp = unwind_user_frame(consume_entry, cookie, fp, 0);
+}