From nobody Thu Sep 24 13:39:20 2026 Received: from mx.itxnorge.no (itx-kvm-14.itxnorge.no [91.189.121.228]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9C1A747ACF4; Wed, 23 Sep 2026 09:04:49 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=91.189.121.228 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790154292; cv=none; b=K1SEYo2GU9GVqkXg9e7xXweem13bS0uuODcP6T0hlW73hY7UtqSgIf2o5UAAmKHAldY28+jEDpV7/8RRJ/r7gigx96/uBqhGUsPTjmuBZfsJsryQ0YPmRpWFlntL+XGEHzqGzdAQt8YBK7o9axbi/BvfL+Aw0GKb4Hz10ubhHNk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790154292; c=relaxed/simple; bh=9I6P5fj4M9aW3lOJEzB4O6F1qrex7/BFTYa9USvHkmI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=PpRX+EM5czI2qfYWYcVVCg6ytmy+VMFjcSS8I2IxQVUVOyJsLdaK9qveQ00gBB/UmTFL6RF2bwMASyKweiUaZnqD/1FU+5VeAm4LAZaA7Dx3m+0pG7wIod9DE0g2+8kisBZGwkUX6hKpb2RqCKElFag5ZIGynq7oeILs/dMLtKE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=itx.no; spf=pass smtp.mailfrom=itx.no; dkim=pass (1024-bit key) header.d=itx.no header.i=@itx.no header.b=nPN0wWNb; arc=none smtp.client-ip=91.189.121.228 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=itx.no Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=itx.no Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=itx.no header.i=@itx.no header.b="nPN0wWNb" From: Stian Halseth DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=itx.no; s=mx.itx.no; t=1790154287; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=ja7ThhMHEJvxh7E25icWLGG63X4wT5FTTa15Zn82Ghw=; b=nPN0wWNb5RfP75MsSW6XxtPQIGnq8JFwrOufmXXViztlxOGGg3O6vxihohnEIm93Cfu1M4 nFiP7bKfImsnKrwP4C4G4bXyw29LgmJOc2fZ56C5aCfttuy5i//BhKehNuf3riwwypTJwx nofx5PSdszwRQ3JMJsB2Xdjl+rPac+A= To: Peter Zijlstra , Ingo Molnar , Andreas Larsson , "David S. Miller" Cc: Arnaldo Carvalho de Melo , Namhyung Kim , Mark Rutland , Alexander Shishkin , Jiri Olsa , Ian Rogers , Adrian Hunter , James Clark , Jonathan Corbet , Shuah Khan , Randy Dunlap , Magnus Lindholm , linux-perf-users@vger.kernel.org, sparclinux@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, Stian Halseth Subject: [RFC PATCH v3 1/3] perf/core: Let an arch prepare and locate the user stack dump Date: Wed, 23 Sep 2026 11:04:26 +0200 Message-ID: <20260923090429.2026529-2-stian@itx.no> In-Reply-To: <20260923090429.2026529-1-stian@itx.no> References: <20260923090429.2026529-1-stian@itx.no> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" PERF_SAMPLE_STACK_USER copies the user stack as it is in memory, starting at the user stack pointer. Two things about sparc64 do not fit that. The sampled window's %l and %i registers, including the frame pointer and return address an unwinder starts from, may still be in the register file: they only reach the stack when a window spills. perf_callchain_user() already handles this for the callchain by calling flushw_user() first; the user stack dump has no equivalent arch entry point. And a 64-bit stack pointer is biased by 2047: the stack starts at %sp + 2047. Starting the dump at the register value spends a quarter of an 8K dump on memory below the frame, and gives no dump at all when that page has never been touched, as for a leaf frame at the deepest point a thread's stack has reached. Add two hooks in the style of perf_arch_misc_flags(): perf_arch_prepare_ustack(), a no-op by default, called from perf_prepare_sample() before the dump size is computed, and perf_arch_user_stack_pointer(), user_stack_pointer() by default, the address the dump starts at. Flushing in the arch PMU interrupt handler instead would not do: software events such as cpu-clock reach perf_event_overflow() without passing through it. Signed-off-by: Stian Halseth Reviewed-by: Ian Rogers Tested-by: Magnus Lindholm --- include/linux/perf_event.h | 11 +++++++++++ kernel/events/core.c | 2 ++ kernel/events/internal.h | 2 +- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h index 5842552294c1..49aa22db43c0 100644 --- a/include/linux/perf_event.h +++ b/include/linux/perf_event.h @@ -1815,6 +1815,17 @@ extern unsigned long perf_instruction_pointer(struct= perf_event *event, #ifndef perf_arch_bpf_user_pt_regs # define perf_arch_bpf_user_pt_regs(regs) regs #endif +/* + * Called before the user stack of the current task is dumped, for an arch + * that still holds part of the user's stack state in registers. + */ +#ifndef perf_arch_prepare_ustack +static inline void perf_arch_prepare_ustack(void) { } +#endif +/* Where the user stack dump starts, for an arch with a biased stack point= er. */ +#ifndef perf_arch_user_stack_pointer +# define perf_arch_user_stack_pointer(regs) user_stack_pointer(regs) +#endif =20 #ifndef perf_arch_guest_misc_flags static inline unsigned long perf_arch_guest_misc_flags(struct pt_regs *reg= s) diff --git a/kernel/events/core.c b/kernel/events/core.c index db7b76d6b68a..90fb35c7d279 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -8718,6 +8718,8 @@ void perf_prepare_sample(struct perf_sample_data *dat= a, u16 header_size =3D perf_sample_data_size(data, event); u16 size =3D sizeof(u64); =20 + if (data->regs_user.regs) + perf_arch_prepare_ustack(); stack_size =3D perf_sample_ustack_size(stack_size, header_size, data->regs_user.regs); =20 diff --git a/kernel/events/internal.h b/kernel/events/internal.h index c03c4f2eea57..01b5f53d01d7 100644 --- a/kernel/events/internal.h +++ b/kernel/events/internal.h @@ -236,7 +236,7 @@ static inline bool arch_perf_have_user_stack_dump(void) return true; } =20 -#define perf_user_stack_pointer(regs) user_stack_pointer(regs) +#define perf_user_stack_pointer(regs) perf_arch_user_stack_pointer(regs) #else static inline bool arch_perf_have_user_stack_dump(void) { --=20 2.55.0 From nobody Thu Sep 24 13:39:20 2026 Received: from mx.itxnorge.no (itx-kvm-14.itxnorge.no [91.189.121.228]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9C10847ACED; Wed, 23 Sep 2026 09:04:49 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=91.189.121.228 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790154292; cv=none; b=IfJ5MZ+m3VOvf0cV82hzMVnuq43dOaTs1bLZdmnBmnNFXb4BQJUVAaL5c7Hgh0i+2xB637sO/P1kmj+NlV6g+t6cd+RkPCfIcBrcHn628uOcRPorKB1nUtDoiQyxl6RoYXjDDPHdeMK7OMDQbz27qAwm8rEd8hVZH1Oe/9kyKb0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790154292; c=relaxed/simple; bh=MnrQ/UCLPgMJiD7M2IUEdTHDvWWbhXS16zP3MUuTmXA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Y83T/zVJgJw6xp1eGDvmtEiQGUypBAweP9nmt4QhSRSWoyjC6RhE5CEilmGq1hBSmKU9AsMEHGzYGuHzRJ6x3TNzTQXimBEXUybFz/Yi3O+0++iPZfahd7VeGLDfvJrG5mGpbmttSavikerabwqP6iN7DykNfaPuSoO3LFHf87Q= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=itx.no; spf=pass smtp.mailfrom=itx.no; dkim=pass (1024-bit key) header.d=itx.no header.i=@itx.no header.b=pdJVR1Ib; arc=none smtp.client-ip=91.189.121.228 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=itx.no Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=itx.no Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=itx.no header.i=@itx.no header.b="pdJVR1Ib" From: Stian Halseth DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=itx.no; s=mx.itx.no; t=1790154287; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=/RH3e8NuNIPbcQndfdh8xaIIhh/m+lM4txfgcsD5MlM=; b=pdJVR1IbUFruVgxUzoEamrAyij23T4L8NpCXabGaVJSlHcUH9K10jaiU3JeBFu24SB2Il5 TmBMqhmG0LGjxFn+MjEYdvkDSpK+chuQtoYboMlD10TuGnLS2UAFtl5/99yd4h999kvX7z rIfKxJaAcLDYh/87YXJew9VRrhzQpis= To: Peter Zijlstra , Ingo Molnar , Andreas Larsson , "David S. Miller" Cc: Arnaldo Carvalho de Melo , Namhyung Kim , Mark Rutland , Alexander Shishkin , Jiri Olsa , Ian Rogers , Adrian Hunter , James Clark , Jonathan Corbet , Shuah Khan , Randy Dunlap , Magnus Lindholm , linux-perf-users@vger.kernel.org, sparclinux@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, Stian Halseth Subject: [RFC PATCH v3 2/3] sparc64: Support PERF_SAMPLE_REGS_USER and PERF_SAMPLE_STACK_USER Date: Wed, 23 Sep 2026 11:04:27 +0200 Message-ID: <20260923090429.2026529-3-stian@itx.no> In-Reply-To: <20260923090429.2026529-1-stian@itx.no> References: <20260923090429.2026529-1-stian@itx.no> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Select HAVE_PERF_REGS and HAVE_PERF_USER_STACK_DUMP and add the perf_regs implementation. The exposed registers mirror struct pt_regs: %g1-%g7, %o0-%o7 (named from the user's point of view; pt_regs calls them UREG_I*), %tstate, NPC and %y, with the PC in the slot of %g0, as mips and loongarch do for their hardwired-zero register. Trap entry does not save %g0, so its pt_regs slot holds stale stack contents for a trap taken in the kernel and must not be exposed. %o6 is reported as held in the register, i.e. with the 2047 stack bias for a 64-bit stack, the same as ptrace and user_stack_pointer(). The stack dump starts at the stack's actual address instead, %o6 + 2047 when test_thread_64bit_stack() says so, so that it begins at the sampled frame; the uapi header documents this for consumers. User-mode values of a 32-bit task are truncated to 32 bits as genregs32_get() does, since the hardware does not zero-extend them. Kernel-mode registers, as seen by PERF_SAMPLE_REGS_INTR, are left alone. perf_arch_prepare_ustack() flushes the user register windows so the sampled window's %l and %i registers are in the dumped stack, where a DWARF unwinder needs them: the CFI of a function after `save` defines the CFA in terms of %i6. This makes perf record --call-graph dwarf and eu-stackprof usable on sparc64. Signed-off-by: Stian Halseth Reviewed-by: Ian Rogers Tested-by: Magnus Lindholm --- .../features/perf/perf-regs/arch-support.txt | 2 +- .../perf/perf-stackdump/arch-support.txt | 2 +- arch/sparc/Kconfig | 2 + arch/sparc/include/asm/perf_event.h | 5 ++ arch/sparc/include/uapi/asm/perf_regs.h | 34 +++++++++ arch/sparc/kernel/Makefile | 2 +- arch/sparc/kernel/perf_regs.c | 74 +++++++++++++++++++ 7 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 arch/sparc/include/uapi/asm/perf_regs.h create mode 100644 arch/sparc/kernel/perf_regs.c diff --git a/Documentation/features/perf/perf-regs/arch-support.txt b/Docum= entation/features/perf/perf-regs/arch-support.txt index 4c9a5a012075..41ab622efa15 100644 --- a/Documentation/features/perf/perf-regs/arch-support.txt +++ b/Documentation/features/perf/perf-regs/arch-support.txt @@ -23,7 +23,7 @@ | riscv: | ok | | s390: | ok | | sh: | TODO | - | sparc: | TODO | + | sparc: | ok | | um: | TODO | | x86: | ok | | xtensa: | TODO | diff --git a/Documentation/features/perf/perf-stackdump/arch-support.txt b/= Documentation/features/perf/perf-stackdump/arch-support.txt index dd362b5cb638..cafa6fae7704 100644 --- a/Documentation/features/perf/perf-stackdump/arch-support.txt +++ b/Documentation/features/perf/perf-stackdump/arch-support.txt @@ -23,7 +23,7 @@ | riscv: | ok | | s390: | ok | | sh: | TODO | - | sparc: | TODO | + | sparc: | ok | | um: | TODO | | x86: | ok | | xtensa: | TODO | diff --git a/arch/sparc/Kconfig b/arch/sparc/Kconfig index ab77d3f2536e..9d7f575f359d 100644 --- a/arch/sparc/Kconfig +++ b/arch/sparc/Kconfig @@ -93,6 +93,8 @@ config SPARC64 select RTC_DRV_SUN4V select RTC_DRV_STARFIRE select HAVE_PERF_EVENTS + select HAVE_PERF_REGS + select HAVE_PERF_USER_STACK_DUMP select PERF_USE_VMALLOC select ARCH_HAVE_NMI_SAFE_CMPXCHG select HAVE_C_RECORDMCOUNT diff --git a/arch/sparc/include/asm/perf_event.h b/arch/sparc/include/asm/p= erf_event.h index c2aec0c7f4f5..3e70c23e7714 100644 --- a/arch/sparc/include/asm/perf_event.h +++ b/arch/sparc/include/asm/perf_event.h @@ -25,6 +25,11 @@ do { \ (regs)->u_regs[UREG_I6] =3D _fp; \ (regs)->u_regs[UREG_I7] =3D _i7; \ } while (0) + +void perf_arch_prepare_ustack(void); +#define perf_arch_prepare_ustack perf_arch_prepare_ustack +unsigned long perf_arch_user_stack_pointer(struct pt_regs *regs); +#define perf_arch_user_stack_pointer perf_arch_user_stack_pointer #endif =20 #endif diff --git a/arch/sparc/include/uapi/asm/perf_regs.h b/arch/sparc/include/u= api/asm/perf_regs.h new file mode 100644 index 000000000000..0621ffbdafce --- /dev/null +++ b/arch/sparc/include/uapi/asm/perf_regs.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _UAPI_ASM_SPARC_PERF_REGS_H +#define _UAPI_ASM_SPARC_PERF_REGS_H + +/* + * Mirrors struct pt_regs, with the PC in the slot of the hardwired-zero + * %g0. O6 is %sp as held in the register: for a 64-bit stack (odd O6) + * it is biased by 2047, and the PERF_SAMPLE_STACK_USER dump starts at + * O6 + 2047. + */ +enum perf_event_sparc_regs { + PERF_REG_SPARC_PC, + PERF_REG_SPARC_G1, + PERF_REG_SPARC_G2, + PERF_REG_SPARC_G3, + PERF_REG_SPARC_G4, + PERF_REG_SPARC_G5, + PERF_REG_SPARC_G6, + PERF_REG_SPARC_G7, + PERF_REG_SPARC_O0, + PERF_REG_SPARC_O1, + PERF_REG_SPARC_O2, + PERF_REG_SPARC_O3, + PERF_REG_SPARC_O4, + PERF_REG_SPARC_O5, + PERF_REG_SPARC_O6, + PERF_REG_SPARC_O7, + PERF_REG_SPARC_TSTATE, + PERF_REG_SPARC_NPC, + PERF_REG_SPARC_Y, + PERF_REG_SPARC_MAX +}; + +#endif /* _UAPI_ASM_SPARC_PERF_REGS_H */ diff --git a/arch/sparc/kernel/Makefile b/arch/sparc/kernel/Makefile index 497b5714fa8f..a3dd92247c3b 100644 --- a/arch/sparc/kernel/Makefile +++ b/arch/sparc/kernel/Makefile @@ -113,7 +113,7 @@ obj-$(CONFIG_AUDIT) +=3D audit.o audit--$(CONFIG_AUDIT) :=3D compat_audit.o obj-$(CONFIG_COMPAT) +=3D $(audit--y) =20 -pc--$(CONFIG_PERF_EVENTS) :=3D perf_event.o +pc--$(CONFIG_PERF_EVENTS) :=3D perf_event.o perf_regs.o obj-$(CONFIG_SPARC64) +=3D $(pc--y) =20 obj-$(CONFIG_UPROBES) +=3D uprobes.o diff --git a/arch/sparc/kernel/perf_regs.c b/arch/sparc/kernel/perf_regs.c new file mode 100644 index 000000000000..8bcc3421ec19 --- /dev/null +++ b/arch/sparc/kernel/perf_regs.c @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include + +u64 perf_reg_value(struct pt_regs *regs, int idx) +{ + u64 val; + + switch (idx) { + case PERF_REG_SPARC_PC: + val =3D regs->tpc; + break; + case PERF_REG_SPARC_G1 ... PERF_REG_SPARC_O7: + val =3D regs->u_regs[idx]; + break; + case PERF_REG_SPARC_TSTATE: + return regs->tstate; + case PERF_REG_SPARC_NPC: + val =3D regs->tnpc; + break; + case PERF_REG_SPARC_Y: + return regs->y; + default: + WARN_ON_ONCE(1); + return 0; + } + + if (user_mode(regs) && test_thread_flag(TIF_32BIT)) + val =3D (u32)val; + + return val; +} + +#define REG_RESERVED (~((1ULL << PERF_REG_SPARC_MAX) - 1)) + +int perf_reg_validate(u64 mask) +{ + if (!mask || mask & REG_RESERVED) + return -EINVAL; + + return 0; +} + +u64 perf_reg_abi(struct task_struct *task) +{ + if (test_tsk_thread_flag(task, TIF_32BIT)) + return PERF_SAMPLE_REGS_ABI_32; + + return PERF_SAMPLE_REGS_ABI_64; +} + +void perf_get_regs_user(struct perf_regs *regs_user, + struct pt_regs *regs) +{ + regs_user->regs =3D task_pt_regs(current); + regs_user->abi =3D perf_reg_abi(current); +} + +void perf_arch_prepare_ustack(void) +{ + flushw_user(); +} + +unsigned long perf_arch_user_stack_pointer(struct pt_regs *regs) +{ + unsigned long sp =3D user_stack_pointer(regs); + + if (test_thread_64bit_stack(sp)) + sp +=3D STACK_BIAS; + return sp; +} --=20 2.55.0 From nobody Thu Sep 24 13:39:20 2026 Received: from mx.itxnorge.no (itx-kvm-14.itxnorge.no [91.189.121.228]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9C24847ACF8; Wed, 23 Sep 2026 09:04:49 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=91.189.121.228 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790154292; cv=none; b=ruA6yMCwotVHGWltQRnaUS2yhmz+iqpKt1Rj3xI6YFVYzgdGbBeUEerX6XfX1uY5Gj25uShbpnqXinMf0asSUm5gdoEMipN5TrPENaezUN41xCRpbsqXWmspMILQ1ESkim8U3l6hVy+/10rXzKlLlUNrcudNHpLtFVC/J3tOlR8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790154292; c=relaxed/simple; bh=cxgetAdP1Sx1ujJnO5LBiKiGZ+DDeQyEAcve0PRNeYI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=FMhiOidYNWdr6RiPGDiWXC1FcqGR87aFXxUWxeRkz2v/MXAyvXxJ7SxByl3HfQExya+mHMXkwXGP/oD2iMyCZ6eKJDVmCaJKjfQRrjkJR/JZDMmyCUw0eMJYlJzNGGYuGxqa00p66IJNb50f/KCREcyy01E1+i7N/agCF2CFlcs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=itx.no; spf=pass smtp.mailfrom=itx.no; dkim=pass (1024-bit key) header.d=itx.no header.i=@itx.no header.b=j5KeeO56; arc=none smtp.client-ip=91.189.121.228 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=itx.no Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=itx.no Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=itx.no header.i=@itx.no header.b="j5KeeO56" From: Stian Halseth DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=itx.no; s=mx.itx.no; t=1790154287; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=YcARCW0fuNDHJ8brXMYCmROpjUHy2tGOJ3XYiDITMp0=; b=j5KeeO56WMTt7d3r1OjG7MCuUIr5V52qtGFMJab9DMzYmLcu3Vdnp3DTJgh7T0chRD+gt9 NQEKiWa0BOB55CqjjhqqcrRXqCWyX8S8nOvl1eQ9HsSFY1CMYOt1Y49Tx47fOH7lnPczG1 cpvqcv0JrdbUKx9sHcCUTX/TbMy9l6Y= To: Peter Zijlstra , Ingo Molnar , Andreas Larsson , "David S. Miller" Cc: Arnaldo Carvalho de Melo , Namhyung Kim , Mark Rutland , Alexander Shishkin , Jiri Olsa , Ian Rogers , Adrian Hunter , James Clark , Jonathan Corbet , Shuah Khan , Randy Dunlap , Magnus Lindholm , linux-perf-users@vger.kernel.org, sparclinux@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, Stian Halseth Subject: [RFC PATCH v3 3/3] perf tools: Support sparc user register samples and dwarf unwinding Date: Wed, 23 Sep 2026 11:04:28 +0200 Message-ID: <20260923090429.2026529-4-stian@itx.no> In-Reply-To: <20260923090429.2026529-1-stian@itx.no> References: <20260923090429.2026529-1-stian@itx.no> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add the sparc perf_regs tables and the EM_SPARC/EM_SPARCV9 dispatch for register names, masks, ip/sp and the perf-to-DWARF register mapping, plus the uapi header copy. For libdw unwinding, seed DWARF registers 16-31 (%l0-%l7, %i0-%i7) from the register save area at the start of the stack dump: the kernel flushes the sampled window there, and the CFI after `save` needs %i6 and %i7 to find the first caller. The dump of a 64-bit sparc stack starts at %sp + 2047, so memory_read() takes its base from there. Signed-off-by: Stian Halseth Reviewed-by: Ian Rogers Tested-by: Magnus Lindholm --- tools/arch/sparc/include/uapi/asm/perf_regs.h | 34 ++++++++++ tools/perf/arch/sparc/include/perf_regs.h | 18 +++++ tools/perf/check-headers.sh | 1 + tools/perf/util/dwarf-regs-arch/Build | 1 + .../util/dwarf-regs-arch/dwarf-regs-sparc.c | 12 ++++ tools/perf/util/dwarf-regs.c | 4 ++ tools/perf/util/include/dwarf-regs.h | 1 + tools/perf/util/perf-regs-arch/Build | 1 + .../util/perf-regs-arch/perf_regs_sparc.c | 67 +++++++++++++++++++ tools/perf/util/perf_regs.c | 18 +++++ tools/perf/util/perf_regs.h | 5 ++ tools/perf/util/unwind-libdw.c | 37 ++++++++++ 12 files changed, 199 insertions(+) create mode 100644 tools/arch/sparc/include/uapi/asm/perf_regs.h create mode 100644 tools/perf/arch/sparc/include/perf_regs.h create mode 100644 tools/perf/util/dwarf-regs-arch/dwarf-regs-sparc.c create mode 100644 tools/perf/util/perf-regs-arch/perf_regs_sparc.c diff --git a/tools/arch/sparc/include/uapi/asm/perf_regs.h b/tools/arch/spa= rc/include/uapi/asm/perf_regs.h new file mode 100644 index 000000000000..0621ffbdafce --- /dev/null +++ b/tools/arch/sparc/include/uapi/asm/perf_regs.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _UAPI_ASM_SPARC_PERF_REGS_H +#define _UAPI_ASM_SPARC_PERF_REGS_H + +/* + * Mirrors struct pt_regs, with the PC in the slot of the hardwired-zero + * %g0. O6 is %sp as held in the register: for a 64-bit stack (odd O6) + * it is biased by 2047, and the PERF_SAMPLE_STACK_USER dump starts at + * O6 + 2047. + */ +enum perf_event_sparc_regs { + PERF_REG_SPARC_PC, + PERF_REG_SPARC_G1, + PERF_REG_SPARC_G2, + PERF_REG_SPARC_G3, + PERF_REG_SPARC_G4, + PERF_REG_SPARC_G5, + PERF_REG_SPARC_G6, + PERF_REG_SPARC_G7, + PERF_REG_SPARC_O0, + PERF_REG_SPARC_O1, + PERF_REG_SPARC_O2, + PERF_REG_SPARC_O3, + PERF_REG_SPARC_O4, + PERF_REG_SPARC_O5, + PERF_REG_SPARC_O6, + PERF_REG_SPARC_O7, + PERF_REG_SPARC_TSTATE, + PERF_REG_SPARC_NPC, + PERF_REG_SPARC_Y, + PERF_REG_SPARC_MAX +}; + +#endif /* _UAPI_ASM_SPARC_PERF_REGS_H */ diff --git a/tools/perf/arch/sparc/include/perf_regs.h b/tools/perf/arch/sp= arc/include/perf_regs.h new file mode 100644 index 000000000000..493fbff9ea16 --- /dev/null +++ b/tools/perf/arch/sparc/include/perf_regs.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef ARCH_PERF_REGS_H +#define ARCH_PERF_REGS_H + +#include +#include +#include "../../../../arch/sparc/include/uapi/asm/perf_regs.h" + +#define PERF_REGS_MASK ((1ULL << PERF_REG_SPARC_MAX) - 1) +#define PERF_REGS_MAX PERF_REG_SPARC_MAX + +#ifdef __arch64__ +#define PERF_SAMPLE_REGS_ABI PERF_SAMPLE_REGS_ABI_64 +#else +#define PERF_SAMPLE_REGS_ABI PERF_SAMPLE_REGS_ABI_32 +#endif + +#endif /* ARCH_PERF_REGS_H */ diff --git a/tools/perf/check-headers.sh b/tools/perf/check-headers.sh index 531c0e0e84df..f144590f5082 100755 --- a/tools/perf/check-headers.sh +++ b/tools/perf/check-headers.sh @@ -42,6 +42,7 @@ declare -a FILES=3D( "arch/mips/include/uapi/asm/perf_regs.h" "arch/powerpc/include/uapi/asm/perf_regs.h" "arch/s390/include/uapi/asm/perf_regs.h" + "arch/sparc/include/uapi/asm/perf_regs.h" "arch/x86/include/uapi/asm/perf_regs.h" "arch/x86/include/uapi/asm/kvm.h" "arch/x86/include/uapi/asm/svm.h" diff --git a/tools/perf/util/dwarf-regs-arch/Build b/tools/perf/util/dwarf-= regs-arch/Build index ceb68ae86fd8..bda944029046 100644 --- a/tools/perf/util/dwarf-regs-arch/Build +++ b/tools/perf/util/dwarf-regs-arch/Build @@ -6,4 +6,5 @@ perf-util-$(CONFIG_LIBDW) +=3D dwarf-regs-mips.o perf-util-$(CONFIG_LIBDW) +=3D dwarf-regs-powerpc.o perf-util-$(CONFIG_LIBDW) +=3D dwarf-regs-riscv.o perf-util-$(CONFIG_LIBDW) +=3D dwarf-regs-s390.o +perf-util-$(CONFIG_LIBDW) +=3D dwarf-regs-sparc.o perf-util-$(CONFIG_LIBDW) +=3D dwarf-regs-x86.o diff --git a/tools/perf/util/dwarf-regs-arch/dwarf-regs-sparc.c b/tools/per= f/util/dwarf-regs-arch/dwarf-regs-sparc.c new file mode 100644 index 000000000000..a5af1e1d8b8b --- /dev/null +++ b/tools/perf/util/dwarf-regs-arch/dwarf-regs-sparc.c @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include "../../../arch/sparc/include/uapi/asm/perf_regs.h" + +int __get_dwarf_regnum_for_perf_regnum_sparc(int perf_regnum) +{ + if (perf_regnum < PERF_REG_SPARC_G1 || perf_regnum > PERF_REG_SPARC_O7) + return -ENOENT; + + return perf_regnum; +} diff --git a/tools/perf/util/dwarf-regs.c b/tools/perf/util/dwarf-regs.c index 797f455eba0d..df9200c31aaa 100644 --- a/tools/perf/util/dwarf-regs.c +++ b/tools/perf/util/dwarf-regs.c @@ -217,6 +217,10 @@ int get_dwarf_regnum_for_perf_regnum(int perf_regnum, = unsigned int machine, case EM_S390: reg =3D __get_dwarf_regnum_for_perf_regnum_s390(perf_regnum); break; + case EM_SPARC: + case EM_SPARCV9: + reg =3D __get_dwarf_regnum_for_perf_regnum_sparc(perf_regnum); + break; case EM_LOONGARCH: reg =3D __get_dwarf_regnum_for_perf_regnum_loongarch(perf_regnum); break; diff --git a/tools/perf/util/include/dwarf-regs.h b/tools/perf/util/include= /dwarf-regs.h index 46a764cf322f..4a02f30c5604 100644 --- a/tools/perf/util/include/dwarf-regs.h +++ b/tools/perf/util/include/dwarf-regs.h @@ -113,6 +113,7 @@ int __get_dwarf_regnum_for_perf_regnum_loongarch(int pe= rf_regnum); int __get_dwarf_regnum_for_perf_regnum_powerpc(int perf_regnum); int __get_dwarf_regnum_for_perf_regnum_riscv(int perf_regnum); int __get_dwarf_regnum_for_perf_regnum_s390(int perf_regnum); +int __get_dwarf_regnum_for_perf_regnum_sparc(int perf_regnum); int __get_dwarf_regnum_for_perf_regnum_mips(int perf_regnum); =20 /* diff --git a/tools/perf/util/perf-regs-arch/Build b/tools/perf/util/perf-re= gs-arch/Build index be95402aa540..47220a2f2dae 100644 --- a/tools/perf/util/perf-regs-arch/Build +++ b/tools/perf/util/perf-regs-arch/Build @@ -6,4 +6,5 @@ perf-util-y +=3D perf_regs_mips.o perf-util-y +=3D perf_regs_powerpc.o perf-util-y +=3D perf_regs_riscv.o perf-util-y +=3D perf_regs_s390.o +perf-util-y +=3D perf_regs_sparc.o perf-util-y +=3D perf_regs_x86.o diff --git a/tools/perf/util/perf-regs-arch/perf_regs_sparc.c b/tools/perf/= util/perf-regs-arch/perf_regs_sparc.c new file mode 100644 index 000000000000..9ee8e496ab8c --- /dev/null +++ b/tools/perf/util/perf-regs-arch/perf_regs_sparc.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include "../perf_regs.h" +#include "../../arch/sparc/include/perf_regs.h" + +uint64_t __perf_reg_mask_sparc(bool intr __maybe_unused) +{ + return PERF_REGS_MASK; +} + +const char *__perf_reg_name_sparc(int id) +{ + switch (id) { + case PERF_REG_SPARC_PC: + return "pc"; + case PERF_REG_SPARC_G1: + return "g1"; + case PERF_REG_SPARC_G2: + return "g2"; + case PERF_REG_SPARC_G3: + return "g3"; + case PERF_REG_SPARC_G4: + return "g4"; + case PERF_REG_SPARC_G5: + return "g5"; + case PERF_REG_SPARC_G6: + return "g6"; + case PERF_REG_SPARC_G7: + return "g7"; + case PERF_REG_SPARC_O0: + return "o0"; + case PERF_REG_SPARC_O1: + return "o1"; + case PERF_REG_SPARC_O2: + return "o2"; + case PERF_REG_SPARC_O3: + return "o3"; + case PERF_REG_SPARC_O4: + return "o4"; + case PERF_REG_SPARC_O5: + return "o5"; + case PERF_REG_SPARC_O6: + return "sp"; + case PERF_REG_SPARC_O7: + return "o7"; + case PERF_REG_SPARC_TSTATE: + return "tstate"; + case PERF_REG_SPARC_NPC: + return "npc"; + case PERF_REG_SPARC_Y: + return "y"; + default: + return NULL; + } + + return NULL; +} + +uint64_t __perf_reg_ip_sparc(void) +{ + return PERF_REG_SPARC_PC; +} + +uint64_t __perf_reg_sp_sparc(void) +{ + return PERF_REG_SPARC_O6; +} diff --git a/tools/perf/util/perf_regs.c b/tools/perf/util/perf_regs.c index 558c143abbab..5880b54fd7dc 100644 --- a/tools/perf/util/perf_regs.c +++ b/tools/perf/util/perf_regs.c @@ -68,6 +68,10 @@ uint64_t perf_intr_reg_mask(uint16_t e_machine) case EM_S390: mask =3D __perf_reg_mask_s390(/*intr=3D*/true); break; + case EM_SPARC: + case EM_SPARCV9: + mask =3D __perf_reg_mask_sparc(/*intr=3D*/true); + break; case EM_386: case EM_X86_64: mask =3D __perf_reg_mask_x86(/*intr=3D*/true); @@ -111,6 +115,10 @@ uint64_t perf_user_reg_mask(uint16_t e_machine) case EM_S390: mask =3D __perf_reg_mask_s390(/*intr=3D*/false); break; + case EM_SPARC: + case EM_SPARCV9: + mask =3D __perf_reg_mask_sparc(/*intr=3D*/false); + break; case EM_386: case EM_X86_64: mask =3D __perf_reg_mask_x86(/*intr=3D*/false); @@ -154,6 +162,10 @@ const char *perf_reg_name(int id, uint16_t e_machine, = uint32_t e_flags) case EM_S390: reg_name =3D __perf_reg_name_s390(id); break; + case EM_SPARC: + case EM_SPARCV9: + reg_name =3D __perf_reg_name_sparc(id); + break; case EM_386: case EM_X86_64: reg_name =3D __perf_reg_name_x86(id); @@ -215,6 +227,9 @@ uint64_t perf_arch_reg_ip(uint16_t e_machine) return __perf_reg_ip_riscv(); case EM_S390: return __perf_reg_ip_s390(); + case EM_SPARC: + case EM_SPARCV9: + return __perf_reg_ip_sparc(); case EM_386: case EM_X86_64: return __perf_reg_ip_x86(); @@ -244,6 +259,9 @@ uint64_t perf_arch_reg_sp(uint16_t e_machine) return __perf_reg_sp_riscv(); case EM_S390: return __perf_reg_sp_s390(); + case EM_SPARC: + case EM_SPARCV9: + return __perf_reg_sp_sparc(); case EM_386: case EM_X86_64: return __perf_reg_sp_x86(); diff --git a/tools/perf/util/perf_regs.h b/tools/perf/util/perf_regs.h index 79be2b791509..3f453d910aa4 100644 --- a/tools/perf/util/perf_regs.h +++ b/tools/perf/util/perf_regs.h @@ -65,6 +65,11 @@ uint64_t __perf_reg_ip_s390(void); uint64_t __perf_reg_sp_s390(void); int __perf_sdt_arg_parse_op_s390(char *old_op, char **new_op); =20 +uint64_t __perf_reg_mask_sparc(bool intr); +const char *__perf_reg_name_sparc(int id); +uint64_t __perf_reg_ip_sparc(void); +uint64_t __perf_reg_sp_sparc(void); + int __perf_sdt_arg_parse_op_x86(char *old_op, char **new_op); uint64_t __perf_reg_mask_x86(bool intr); const char *__perf_reg_name_x86(int id); diff --git a/tools/perf/util/unwind-libdw.c b/tools/perf/util/unwind-libdw.c index 63a5c2253174..d2adf7f2d8bb 100644 --- a/tools/perf/util/unwind-libdw.c +++ b/tools/perf/util/unwind-libdw.c @@ -4,7 +4,9 @@ #include #include #include +#include #include +#include #include "debug.h" #include "dso.h" #include @@ -225,6 +227,14 @@ static int access_dso_mem(struct unwind_info *ui, Dwar= f_Addr addr, return -1; } =20 +/* A 64-bit sparc stack (odd %sp) is biased; its dump starts past the bias= . */ +static u64 stack_dump_start(uint16_t e_machine, u64 sp) +{ + if ((e_machine =3D=3D EM_SPARC || e_machine =3D=3D EM_SPARCV9) && (sp & 1= )) + return sp + 2047; + return sp; +} + static bool memory_read(Dwfl *dwfl __maybe_unused, Dwarf_Addr addr, Dwarf_= Word *result, void *arg) { @@ -243,6 +253,7 @@ static bool memory_read(Dwfl *dwfl __maybe_unused, Dwar= f_Addr addr, Dwarf_Word * if (ret) return false; =20 + start =3D stack_dump_start(ui->e_machine, start); end =3D start + stack->size; =20 /* Check overflow. */ @@ -267,6 +278,26 @@ static bool memory_read(Dwfl *dwfl __maybe_unused, Dwa= rf_Addr addr, Dwarf_Word * return true; } =20 +/* + * The sampled window's %l and %i registers (DWARF 16-31) are not in the + * sample; the kernel flushed them to the register save area at the start + * of the dumped stack. + */ +static void libdw_set_sparc_window_registers(struct unwind_info *ui, + struct regs_dump *user_regs, + Dwarf_Word *dwarf_regs) +{ + struct stack_dump *stack =3D &ui->sample->user_stack; + u64 sp; + + if (perf_reg_value(&sp, user_regs, perf_arch_reg_sp(ui->e_machine))) + return; + if (!(sp & 1) || 16 * sizeof(u64) > stack->size) + return; + + memcpy(&dwarf_regs[16], stack->data, 16 * sizeof(u64)); +} + static bool libdw_set_initial_registers(Dwfl_Thread *thread, void *arg) { struct dwfl_ui_thread_info *dwfl_ui_ti =3D arg; @@ -276,6 +307,7 @@ static bool libdw_set_initial_registers(Dwfl_Thread *th= read, void *arg) int max_dwarf_reg =3D 0; bool ret; uint16_t e_machine =3D ui->e_machine; + bool is_sparc =3D e_machine =3D=3D EM_SPARC || e_machine =3D=3D EM_SPARCV= 9; int e_flags =3D ui->e_flags; uint64_t ip_perf_reg =3D perf_arch_reg_ip(e_machine); Dwarf_Word val =3D 0; @@ -295,6 +327,9 @@ static bool libdw_set_initial_registers(Dwfl_Thread *th= read, void *arg) max_dwarf_reg =3D dwarf_reg; } } + /* libdw_set_sparc_window_registers() fills DWARF 16-31. */ + if (is_sparc && max_dwarf_reg < 31) + max_dwarf_reg =3D 31; =20 dwarf_regs =3D calloc(max_dwarf_reg + 1, sizeof(*dwarf_regs)); if (!dwarf_regs) @@ -313,6 +348,8 @@ static bool libdw_set_initial_registers(Dwfl_Thread *th= read, void *arg) } } } + if (is_sparc) + libdw_set_sparc_window_registers(ui, user_regs, dwarf_regs); if (perf_reg_value(&val, user_regs, ip_perf_reg) =3D=3D 0) dwfl_thread_state_register_pc(thread, val); =20 --=20 2.55.0