perf riscv: Add perf_regs_load and enable the dwarf unwind test

riscv has no arch level perf test: tests/builtin-test.c only picks up
arch_tests[] for x86, arm64 and powerpc64, tests/Build only builds the
generic dwarf unwind test driver for x86, arm, arm64 and powerpc, and
arch/riscv/tests/ did not exist. "Test dwarf unwind" was therefore never
registered on riscv, even though DWARF is the only reliable way to walk a
riscv user stack -- the arch callchain code walks frame pointers only --
and both unwinders already support riscv, elfutils libdw generically and
libunwind through util/libunwind-arch/libunwind-riscv.c.

Add perf_regs_load() and the arch unwind sample helper modelled on the
arm64 ones, and wire riscv into the two gates. The x1..x31 slots of enum
perf_event_riscv_regs match the hardware registers one to one, and
PERF_REG_RISCV_PC is filled with ra so that unwinding starts at the
caller.

Reviewed-by: Ian Rogers <irogers@google.com>
Signed-off-by: Chen Pei <cp0613@linux.alibaba.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
This commit is contained in:
Chen Pei 2026-09-14 20:41:45 +08:00 committed by Arnaldo Carvalho de Melo
parent 1622c9ae39
commit 0b26b346a8
9 changed files with 151 additions and 2 deletions

View File

@ -1 +1,2 @@
perf-util-y += util/
perf-test-y += tests/

View File

@ -0,0 +1,9 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef ARCH_TESTS_H
#define ARCH_TESTS_H
struct test_suite;
extern struct test_suite *arch_tests[];
#endif

View File

@ -8,6 +8,8 @@
#include <linux/types.h>
#include "../../../../arch/riscv/include/uapi/asm/perf_regs.h"
void perf_regs_load(u64 *regs);
#define PERF_REGS_MASK ((1ULL << PERF_REG_RISCV_MAX) - 1)
#define PERF_REGS_MAX PERF_REG_RISCV_MAX

View File

@ -0,0 +1,4 @@
perf-test-y += regs_load.o
perf-test-$(CONFIG_DWARF_UNWIND) += dwarf-unwind.o
perf-test-y += arch-tests.o

View File

@ -0,0 +1,10 @@
// SPDX-License-Identifier: GPL-2.0
#include "tests/tests.h"
#include "arch-tests.h"
struct test_suite *arch_tests[] = {
#ifdef HAVE_DWARF_UNWIND_SUPPORT
&suite__dwarf_unwind,
#endif
NULL,
};

View File

@ -0,0 +1,64 @@
// SPDX-License-Identifier: GPL-2.0
#include <string.h>
#include "perf_regs.h"
#include "thread.h"
#include "map.h"
#include "maps.h"
#include "event.h"
#include "debug.h"
#include "tests/tests.h"
#define STACK_SIZE 8192
static int sample_ustack(struct perf_sample *sample,
struct thread *thread, u64 *regs)
{
struct stack_dump *stack = &sample->user_stack;
struct map *map;
unsigned long sp;
u64 stack_size, *buf;
buf = malloc(STACK_SIZE);
if (!buf) {
pr_debug("failed to allocate sample uregs data\n");
return -1;
}
sp = (unsigned long) regs[PERF_REG_RISCV_SP];
map = maps__find(thread__maps(thread), (u64)sp);
if (!map) {
pr_debug("failed to get stack map\n");
free(buf);
return -1;
}
stack_size = map__end(map) - sp;
map__put(map);
stack_size = stack_size > STACK_SIZE ? STACK_SIZE : stack_size;
memcpy(buf, (void *) sp, stack_size);
stack->data = (char *) buf;
stack->size = stack_size;
return 0;
}
int test__arch_unwind_sample(struct perf_sample *sample,
struct thread *thread)
{
struct regs_dump *regs = perf_sample__user_regs(sample);
u64 *buf;
buf = calloc(PERF_REGS_MAX, sizeof(u64));
if (!buf) {
pr_debug("failed to allocate sample uregs data\n");
return -1;
}
perf_regs_load(buf);
regs->abi = PERF_SAMPLE_REGS_ABI;
regs->regs = buf;
regs->mask = PERF_REGS_MASK;
return sample_ustack(sample, thread, buf);
}

View File

@ -0,0 +1,58 @@
/* SPDX-License-Identifier: GPL-2.0 */
#include <linux/linkage.h>
/*
* Fill the u64 array 'regs' at the slots of enum perf_event_riscv_regs:
* x1..x31 map one-to-one onto PERF_REG_RISCV_RA..PERF_REG_RISCV_T6, and the
* stride stays 8 bytes on RV32 because the slots are u64 and the caller
* zeroes them.
*
* PERF_REG_RISCV_PC is filled with ra, the return address of the call to
* this function, so that unwinding starts at the caller rather than here.
*/
#if __riscv_xlen == 64
#define REG_S sd
#else
#define REG_S sw
#endif
#define STR_REG(r) REG_S x##r, 8 * r(a0)
.text
.type perf_regs_load,%function
SYM_FUNC_START(perf_regs_load)
STR_REG(1)
STR_REG(2)
STR_REG(3)
STR_REG(4)
STR_REG(5)
STR_REG(6)
STR_REG(7)
STR_REG(8)
STR_REG(9)
STR_REG(10)
STR_REG(11)
STR_REG(12)
STR_REG(13)
STR_REG(14)
STR_REG(15)
STR_REG(16)
STR_REG(17)
STR_REG(18)
STR_REG(19)
STR_REG(20)
STR_REG(21)
STR_REG(22)
STR_REG(23)
STR_REG(24)
STR_REG(25)
STR_REG(26)
STR_REG(27)
STR_REG(28)
STR_REG(29)
STR_REG(30)
STR_REG(31)
REG_S ra, 0(a0)
ret
SYM_FUNC_END(perf_regs_load)

View File

@ -73,7 +73,7 @@ perf-test-y += tool_pmu.o
perf-test-y += subcmd-help.o
perf-test-y += kallsyms-split.o
ifeq ($(SRCARCH),$(filter $(SRCARCH),x86 arm arm64 powerpc))
ifeq ($(SRCARCH),$(filter $(SRCARCH),x86 arm arm64 powerpc riscv))
perf-test-$(CONFIG_DWARF_UNWIND) += dwarf-unwind.o
endif

View File

@ -69,7 +69,8 @@ static const char *workload_control;
* dependent on the initialization, as such GCC with LTO complains of
* conflicting definitions with a weak symbol.
*/
#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || defined(__powerpc64__)
#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || \
defined(__powerpc64__) || defined(__riscv)
extern struct test_suite *arch_tests[];
#else
static struct test_suite *arch_tests[] = {