[PATCH] fixup! unwind_user/sframe: Add support for reading .sframe contents
From: Jens Remus <hidden>
Date: 2025-05-28 10:27:43
Also in:
linux-perf-users, linux-toolchains, lkml
Subsystem:
the rest, userspace stack unwinding · Maintainers:
Linus Torvalds, Josh Poimboeuf, Steven Rostedt
---
Notes (jremus):
Link: https://lore.kernel.org/all/b35ca3a3-8de5-4d32-8d30-d4e562f6b0de@linux.ibm.com/ (local)
The struct sframe_fre field ip_off must be u32, as the SFrame FRE start
address (sfre_start_address) is unsigned 8-bit, 16-bit, or 32-bit:
https://sourceware.org/binutils/docs/sframe-spec.html#SFrame-Frame-Row-Entries
When searching for a FRE of a FDE for an IP, the IP offset from function
start address (ip_off = ip - (sec->sframe_start + fde->start_addr)) is
always positive, as the search for a FDE for the same IP returned a
FDE with: sec->sframe_start + fde->start_addr <= ip
This enables comparison against the unsigned FDE ip_off.
This fixup includes a proposed fix from Josh (with minor modification
due to duplicate macro names) to correctly perform sign extension when
reading (un-)signed SFrame FDE/FRE fields:
https://lore.kernel.org/all/20250207210614.nks6bxad4jhdulwg@jpoimboe/ (local)
kernel/unwind/sframe.c | 38 +++++++++++++++++++++++++++-----------
1 file changed, 27 insertions(+), 11 deletions(-)
diff --git a/kernel/unwind/sframe.c b/kernel/unwind/sframe.c
index 3f7cc9bc27eb..8804ac59edfa 100644
--- a/kernel/unwind/sframe.c
+++ b/kernel/unwind/sframe.c@@ -19,7 +19,7 @@ struct sframe_fre { unsigned int size; - s32 ip_off; + u32 ip_off; s32 cfa_off; s32 ra_off; s32 fp_off;
@@ -129,33 +129,48 @@ static __always_inline int __find_fde(struct sframe_section *sec, return -EFAULT; } -#define __UNSAFE_GET_USER_INC(to, from, type, label) \ +#define ____UNSAFE_GET_USER_INC(to, from, type, label) \ ({ \ type __to; \ unsafe_get_user(__to, (type __user *)from, label); \ from += sizeof(__to); \ - to = (typeof(to))__to; \ + to = __to; \ }) -#define UNSAFE_GET_USER_INC(to, from, size, label) \ +#define __UNSAFE_GET_USER_INC(to, from, size, label, u_or_s) \ ({ \ switch (size) { \ case 1: \ - __UNSAFE_GET_USER_INC(to, from, u8, label); \ + ____UNSAFE_GET_USER_INC(to, from, u_or_s##8, label); \ break; \ case 2: \ - __UNSAFE_GET_USER_INC(to, from, u16, label); \ + ____UNSAFE_GET_USER_INC(to, from, u_or_s##16, label); \ break; \ case 4: \ - __UNSAFE_GET_USER_INC(to, from, u32, label); \ + ____UNSAFE_GET_USER_INC(to, from, u_or_s##32, label); \ break; \ default: \ - dbg_sec_uaccess("%d: bad UNSAFE_GET_USER_INC size %u\n",\ + dbg_sec_uaccess("%d: bad unsafe_get_user() size %u\n", \ __LINE__, size); \ return -EFAULT; \ } \ }) +#define UNSAFE_GET_USER_UNSIGNED_INC(to, from, size, label) \ + __UNSAFE_GET_USER_INC(to, from, size, label, u) + +#define UNSAFE_GET_USER_SIGNED_INC(to, from, size, label) \ + __UNSAFE_GET_USER_INC(to, from, size, label, s) + +#define UNSAFE_GET_USER_INC(to, from, size, label) \ + _Generic(to, \ + u8: UNSAFE_GET_USER_UNSIGNED_INC(to, from, size, label), \ + u16: UNSAFE_GET_USER_UNSIGNED_INC(to, from, size, label), \ + u32: UNSAFE_GET_USER_UNSIGNED_INC(to, from, size, label), \ + s8: UNSAFE_GET_USER_SIGNED_INC(to, from, size, label), \ + s16: UNSAFE_GET_USER_SIGNED_INC(to, from, size, label), \ + s32: UNSAFE_GET_USER_SIGNED_INC(to, from, size, label)) + static __always_inline int __read_fre(struct sframe_section *sec, struct sframe_fde *fde, unsigned long fre_addr,
@@ -164,7 +179,8 @@ static __always_inline int __read_fre(struct sframe_section *sec, unsigned char fde_type = SFRAME_FUNC_FDE_TYPE(fde->info); unsigned char fre_type = SFRAME_FUNC_FRE_TYPE(fde->info); unsigned char offset_count, offset_size; - s32 ip_off, cfa_off, ra_off, fp_off; + u32 ip_off; + s32 cfa_off, ra_off, fp_off; unsigned long cur = fre_addr; unsigned char addr_size; u8 info;
@@ -248,9 +264,9 @@ static __always_inline int __find_fre(struct sframe_section *sec, unsigned long fre_addr; bool which = false; unsigned int i; - s32 ip_off; + u32 ip_off; - ip_off = (s32)(ip - sec->sframe_start) - fde->start_addr; + ip_off = ip - (sec->sframe_start + fde->start_addr); if (fde_type == SFRAME_FDE_TYPE_PCMASK) ip_off %= fde->rep_size;
--
2.45.2