All of lore.kernel.org
 help / color / mirror / Atom feed
From: Jens Remus <jremus@linux.ibm.com>
To: linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org,
	linux-s390@vger.kernel.org, x86@kernel.org,
	Steven Rostedt <rostedt@kernel.org>,
	Josh Poimboeuf <jpoimboe@kernel.org>,
	Peter Zijlstra <peterz@infradead.org>,
	Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
Cc: Jens Remus <jremus@linux.ibm.com>,
	Heiko Carstens <hca@linux.ibm.com>,
	Vasily Gorbik <gor@linux.ibm.com>,
	Alexander Gordeev <agordeev@linux.ibm.com>,
	Ilya Leoshkevich <iii@linux.ibm.com>,
	Indu Bhagat <ibhagatgnu@gmail.com>,
	Thomas Gleixner <tglx@kernel.org>, Ingo Molnar <mingo@redhat.com>,
	Borislav Petkov <bp@alien8.de>,
	Dave Hansen <dave.hansen@linux.intel.com>,
	"H. Peter Anvin" <hpa@zytor.com>,
	Namhyung Kim <namhyung@kernel.org>,
	Andrii Nakryiko <andrii@kernel.org>, Kees Cook <kees@kernel.org>,
	Sam James <sam@gentoo.org>
Subject: [RFC PATCH v1 09/25] unwind_user/eh_frame: Add support for reading .eh_frame section
Date: Tue, 18 Aug 2026 16:49:38 +0200	[thread overview]
Message-ID: <20260818144954.2320378-10-jremus@linux.ibm.com> (raw)
In-Reply-To: <20260818144954.2320378-1-jremus@linux.ibm.com>

In preparation for using .eh_frame to unwind user space stacks, add an
eh_frame_find() interface for finding the .eh_frame[_hdr] information
associated with a given text address.

The implementation parses DWARF Call Frame Information (CFI) from the
.eh_frame section, including:
- Common Information Entries (CIEs) that define default unwinding rules,
- Frame Description Entries (FDEs) that describe unwinding for specific
  function address ranges, and
- DWARF CFI instructions that specify how to restore registers.

For performance, use user_read_access_begin() and the corresponding
unsafe_*() accessors.  Note that use of pr_debug() in uaccess-enabled
regions would break noinstr validation, so there aren't any debug
messages yet.  That will be added in a subsequent commit.

Based on Josh Poimboeuf's, Steven Rostedt's, and my unwind user sframe
implementation.

Signed-off-by: Jens Remus <jremus@linux.ibm.com>
---
 MAINTAINERS                                |   2 +
 include/asm-generic/Kbuild                 |   1 +
 include/asm-generic/unwind_user_eh_frame.h |  43 +
 include/linux/eh_frame.h                   |   7 +
 kernel/unwind/eh_frame.c                   | 943 ++++++++++++++++++++-
 kernel/unwind/eh_frame.h                   |  38 +
 kernel/unwind/eh_frame_debug.h             |  19 +
 7 files changed, 1048 insertions(+), 5 deletions(-)
 create mode 100644 include/asm-generic/unwind_user_eh_frame.h
 create mode 100644 kernel/unwind/eh_frame_debug.h

diff --git a/MAINTAINERS b/MAINTAINERS
index f19f5bb87f00..d5f46f26996f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -28202,7 +28202,9 @@ M:	Josh Poimboeuf <jpoimboe@kernel.org>
 M:	Steven Rostedt <rostedt@goodmis.org>
 S:	Maintained
 F:	arch/*/include/asm/unwind_user.h
+F:	arch/*/include/asm/unwind_user_eh_frame.h
 F:	include/asm-generic/unwind_user.h
+F:	include/asm-generic/unwind_user_eh_frame.h
 F:	include/linux/eh_frame.h
 F:	include/linux/unwind*.h
 F:	kernel/unwind/
diff --git a/include/asm-generic/Kbuild b/include/asm-generic/Kbuild
index 15df9dcb42a5..bd031eb6adc4 100644
--- a/include/asm-generic/Kbuild
+++ b/include/asm-generic/Kbuild
@@ -62,6 +62,7 @@ mandatory-y += topology.h
 mandatory-y += trace_clock.h
 mandatory-y += uaccess.h
 mandatory-y += unwind_user.h
+mandatory-y += unwind_user_eh_frame.h
 mandatory-y += vermagic.h
 mandatory-y += vga.h
 mandatory-y += video.h
diff --git a/include/asm-generic/unwind_user_eh_frame.h b/include/asm-generic/unwind_user_eh_frame.h
new file mode 100644
index 000000000000..e6d207597206
--- /dev/null
+++ b/include/asm-generic/unwind_user_eh_frame.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _ASM_GENERIC_UNWIND_USER_EH_FRAME_H
+#define _ASM_GENERIC_UNWIND_USER_EH_FRAME_H
+
+#ifndef EH_FRAME_MAX_CIE_LENGTH
+#define EH_FRAME_MAX_CIE_LENGTH 128
+#endif
+
+#ifndef EH_FRAME_MAX_AUGSTR_LENGTH
+#define EH_FRAME_MAX_AUGSTR_LENGTH 16
+#endif
+
+#ifndef EH_FRAME_MAX_STATE_STACK
+#define EH_FRAME_MAX_STATE_STACK 8
+#endif
+
+#ifndef EH_FRAME_MAX_CODE_ALIGN
+#define EH_FRAME_MAX_CODE_ALIGN 8
+#endif
+
+#ifndef EH_FRAME_MIN_DATA_ALIGN
+#define EH_FRAME_MIN_DATA_ALIGN -8
+#endif
+
+#ifndef EH_FRAME_MAX_DATA_ALIGN
+#define EH_FRAME_MAX_DATA_ALIGN -1
+#endif
+
+#ifndef EH_FRAME_SP_VAL_OFFSET
+/* Most archs/ABIs define CFA as SP at call site, so that SP = CFA + 0 */
+#define EH_FRAME_SP_VAL_OFFSET 0
+#endif
+
+#ifndef eh_frame_reject_sp_rule
+static inline bool eh_frame_reject_sp_rule(void)
+{
+	return true;
+}
+#define eh_frame_reject_sp_rule eh_frame_reject_sp_rule
+#endif
+
+#endif /* _ASM_GENERIC_UNWIND_USER_EH_FRAME_H */
+
diff --git a/include/linux/eh_frame.h b/include/linux/eh_frame.h
index 95df4911fd23..e33041e4f9c0 100644
--- a/include/linux/eh_frame.h
+++ b/include/linux/eh_frame.h
@@ -4,6 +4,7 @@
 
 #include <linux/mm_types.h>
 #include <linux/srcu.h>
+#include <linux/unwind_user_types.h>
 
 #ifdef CONFIG_HAVE_UNWIND_USER_EH_FRAME
 
@@ -32,6 +33,7 @@ extern int eh_frame_add_section(unsigned long eh_frame_hdr_start,
 				unsigned long text_start,
 				unsigned long text_end);
 extern int eh_frame_remove_section(unsigned long eh_frame_hdr_start);
+extern int eh_frame_find(unsigned long ip, struct unwind_user_frame *frame);
 
 static inline bool current_has_eh_frame(void)
 {
@@ -58,6 +60,11 @@ static inline int eh_frame_remove_section(unsigned long eh_frame_hdr_start)
 	return -ENOSYS;
 }
 
+static inline int eh_frame_find(unsigned long ip, struct unwind_user_frame *frame)
+{
+	return -ENOSYS;
+}
+
 static inline bool current_has_eh_frame(void) { return false; }
 
 #endif /* CONFIG_HAVE_UNWIND_USER_EH_FRAME */
diff --git a/kernel/unwind/eh_frame.c b/kernel/unwind/eh_frame.c
index 7bdf7589466a..5ef8fef06a80 100644
--- a/kernel/unwind/eh_frame.c
+++ b/kernel/unwind/eh_frame.c
@@ -13,14 +13,83 @@
 #include <linux/string_helpers.h>
 #include <linux/eh_frame.h>
 #include <linux/unwind_user_types.h>
+#include <asm/unwind_user_eh_frame.h>
 
 #include "eh_frame.h"
-
-#define dbg(fmt, ...)							\
-	pr_debug("%s (%d): " fmt, current->comm, current->pid, ##__VA_ARGS__)
+#include "eh_frame_debug.h"
+
+/* Register state for CFI interpreter */
+enum eh_frame_cfa_rule {
+	CFA_UNDEFINED,		/* unrecoverable */
+	CFA_REG_OFFSET,		/* CFA = reg + offset */
+};
+
+enum eh_frame_reg_rule {
+	REG_UNDEFINED_IMPLICIT,	/* reg = reg */
+	REG_UNDEFINED_EXPLICIT,	/* unrecoverable; RA: outermost frame */
+	REG_SAME_VALUE,		/* reg = reg; TODO: reset to CIE initial CFI */
+	REG_OFFSET,		/* reg = *(CFA + offset) */
+	REG_VAL_OFFSET,		/* reg = CFA + offset */
+	REG_REGISTER,		/* reg = other_reg */
+};
+
+enum eh_frame_reg_index {
+	FP_IDX,			/* frame pointer (FP) */
+	RA_IDX,			/* return address (RA) */
+	NR_REGS
+};
+
+struct eh_frame_reg_state {
+	/* CFA recovery rule */
+	enum eh_frame_cfa_rule cfa_rule;
+	unsigned long cfa_regnum;
+	long cfa_offset;
+
+	/* FP and RA recovery rules (SP uses implicit recovery) */
+	enum eh_frame_reg_rule reg_rule[NR_REGS];
+	unsigned long reg_regnum[NR_REGS];
+	long reg_offset[NR_REGS];
+};
+
+struct eh_frame_cfi_context {
+	struct eh_frame_reg_state state;
+	struct eh_frame_reg_state stack[EH_FRAME_MAX_STATE_STACK];
+	int stack_depth;
+};
+
+struct eh_frame_cie {
+	unsigned long cfi_insn_start;
+	unsigned long cfi_insn_end;
+	int data_align;
+	unsigned int code_align;
+	u8 fde_addr_enc;		/* from CIE 'R' augmentation */
+	bool aug_data_present;		/* from CIE 'z' augmentation */
+	bool signal_frame;		/* from CIE 'S' augmentation */
+};
+
+struct eh_frame_fde {
+	unsigned long func_addr;
+	unsigned long func_size;
+	unsigned long cfi_insn_start;
+	unsigned long cfi_insn_end;
+
+	struct eh_frame_cie cie;	/* referenced CIE*/
+};
 
 DEFINE_STATIC_SRCU(eh_frame_srcu);
 
+#define GET_USER_INC(to, from, end)					\
+({									\
+	typeof(to) __to;						\
+	int ret;							\
+	if (from + sizeof(__to) > end)					\
+		return -EINVAL;						\
+	ret = get_user(__to, (typeof(to) __user *)from);		\
+	from += sizeof(__to);						\
+	to = __to;							\
+	ret;								\
+})
+
 #define UNSAFE_GET_USER_INC(to, from, end, label)			\
 ({									\
 	typeof(to) __to;						\
@@ -111,6 +180,7 @@ static __always_inline int encoded_pointer_size(u8 encoding)
 }
 
 static __always_inline int read_encoded_pointer(struct eh_frame_section *sec,
+						struct eh_frame_fde *fde,
 						unsigned long *addr,
 						unsigned long end,
 						u8 encoding,
@@ -140,6 +210,10 @@ static __always_inline int read_encoded_pointer(struct eh_frame_section *sec,
 		result = sec->text_start;
 		break;
 	case DW_EH_PE_funcrel:
+		if (!fde)
+			return -EINVAL;
+		result = fde->func_addr;
+		break;
 	case DW_EH_PE_aligned:
 		return -EOPNOTSUPP;
 	default:
@@ -219,6 +293,865 @@ static __always_inline int read_encoded_pointer(struct eh_frame_section *sec,
 	return -EFAULT;
 }
 
+static __always_inline int __read_cie(struct eh_frame_section *sec,
+				      unsigned long cie_addr,
+				      struct eh_frame_cie *cie)
+{
+	void __user *cie_ptr = (void __user *)cie_addr;
+	unsigned long cur = cie_addr, end;
+	u32 length, cie_id;
+	u8 version;
+	char aug_str[EH_FRAME_MAX_AUGSTR_LENGTH];
+	int aug_idx;
+	unsigned long code_align;
+	long data_align;
+	u8 ra_reg;
+	bool aug_data_present = false;
+	unsigned long aug_data_len, aug_data_end = cie_addr;
+	u8 fde_addr_enc = DW_EH_PE_absptr;
+	bool signal_frame = false;
+	int ret;
+
+	/* Read CIE length */
+	ret = GET_USER_INC(length, cur, sec->eh_frame_vma_end);
+	if (ret)
+		return ret;
+	if (!length || length == EH_FRAME_DWARF64_LENGTH || length > EH_FRAME_MAX_CIE_LENGTH)
+		return -EINVAL;
+	end = cie_addr + 4 + length;
+	if (end > sec->eh_frame_vma_end)
+		return -EFAULT;
+
+	scoped_user_read_access_size(cie_ptr, 4 + length, Efault) {
+		/* Read CIE_ID (must be 0 for CIE; FDE otherwise) */
+		UNSAFE_GET_USER_INC(cie_id, cur, end, Efault);
+		if (cie_id != EH_FRAME_CIE_ID)
+			return -EINVAL;
+
+		/* Read version */
+		UNSAFE_GET_USER_INC(version, cur, end, Efault);
+		if (version != 1)
+			return -EOPNOTSUPP;
+
+		/* Read augmentation string */
+		for (aug_idx = 0; aug_idx < sizeof(aug_str); aug_idx++) {
+			UNSAFE_GET_USER_INC(aug_str[aug_idx], cur, end, Efault);
+			if (aug_str[aug_idx] == '\0')
+				break;
+		}
+		if (aug_idx >= sizeof(aug_str))
+			return -EINVAL;
+
+		/* Read code alignment factor */
+		ret = read_uleb128(&cur, end, &code_align);
+		if (ret)
+			return ret;
+		if (!code_align || code_align > EH_FRAME_MAX_CODE_ALIGN)
+			return -EINVAL;
+
+		/* Read data alignment factor */
+		ret = read_sleb128(&cur, end, &data_align);
+		if (ret)
+			return ret;
+		if (!data_align || (data_align < EH_FRAME_MIN_DATA_ALIGN ||
+				    data_align > EH_FRAME_MAX_DATA_ALIGN))
+			return -EINVAL;
+
+		/* Read return address register number */
+		UNSAFE_GET_USER_INC(ra_reg, cur, end, Efault);
+		if (ra_reg != EH_FRAME_REG_RA)
+			return -EOPNOTSUPP;
+
+		/* Parse augmentation string and read augmentation data if present */
+		for (aug_idx = 0; aug_str[aug_idx]; aug_idx++) {
+			switch (aug_str[aug_idx]) {
+			case 'z':
+				/* Augmentation data present - must be first character */
+				if (aug_idx != 0)
+					return -EINVAL;
+				aug_data_present = true;
+				ret = read_uleb128(&cur, end, &aug_data_len);
+				if (ret)
+					return ret;
+				aug_data_end = cur + aug_data_len;
+				if (aug_data_end > end)
+					return -EINVAL;
+				break;
+			case 'L': {
+				/* LSDA encoding - skip */
+				u8 lsda_enc;
+				if (!aug_data_present)
+					return -EINVAL;
+				UNSAFE_GET_USER_INC(lsda_enc, cur, aug_data_end, Efault);
+				break;
+			}
+			case 'P': {
+				/* Personality encoding and routine - skip */
+				u8 personality_enc;
+				unsigned long personality_rtn;
+				if (!aug_data_present)
+					return -EINVAL;
+				UNSAFE_GET_USER_INC(personality_enc, cur, aug_data_end, Efault);
+				/*
+				 * Clear indirect flag to avoid user read from
+				 * arbitrary address; still skip field.
+				 */
+				personality_enc &= ~DW_EH_PE_indirect;
+				ret = read_encoded_pointer(sec, NULL, &cur, aug_data_end,
+							   personality_enc, &personality_rtn);
+				if (ret)
+					return ret;
+				break;
+			}
+			case 'R':
+				/* FDE encoding */
+				if (!aug_data_present)
+					return -EINVAL;
+				UNSAFE_GET_USER_INC(fde_addr_enc, cur, aug_data_end, Efault);
+				break;
+			case 'S':
+				/* Signal frame */
+				signal_frame = true;
+				break;
+			default:
+				/* Unknown augmentation */
+				return -EOPNOTSUPP;
+			}
+		}
+	}
+
+	cie->code_align		= code_align;
+	cie->data_align		= data_align;
+	cie->fde_addr_enc	= fde_addr_enc;
+	cie->aug_data_present	= aug_data_present;
+	cie->signal_frame	= signal_frame;
+	cie->cfi_insn_start	= cur;
+	cie->cfi_insn_end	= end;
+
+	return 0;
+
+Efault:
+	return -EFAULT;
+}
+
+static __always_inline int __read_fde(struct eh_frame_section *sec,
+				      unsigned long fde_addr,
+				      struct eh_frame_fde *fde)
+{
+	void __user *fde_ptr = (void __user *)fde_addr;
+	unsigned long cur = fde_addr, end;
+	u32 length, cie_offset;
+	unsigned long cie_addr, func_addr, func_size;
+	int ret;
+
+	/* Read FDE length */
+	ret = GET_USER_INC(length, cur, sec->eh_frame_vma_end);
+	if (ret)
+		return ret;
+	if (!length || length == EH_FRAME_DWARF64_LENGTH)
+		return -EINVAL;
+	end = fde_addr + 4 + length;
+	if (end > sec->eh_frame_vma_end)
+		return -EFAULT;
+
+	scoped_user_read_access_size(fde_ptr, 4 + length, Efault) {
+		/* Read CIE pointer (offset from current position) */
+		UNSAFE_GET_USER_INC(cie_offset, cur, end, Efault);
+		cie_addr = cur - 4 - cie_offset;
+		if (cie_addr + EH_FRAME_CIE_MIN_LENGTH > fde_addr)
+			return -EINVAL;
+		if (cie_addr < sec->eh_frame_start)
+			return -EINVAL;
+	}
+
+	/* Read the CIE to populate alignment factors, RA register, and FDE encoding */
+	ret = __read_cie(sec, cie_addr, &fde->cie);
+	if (ret)
+		return ret;
+
+	scoped_user_read_access_size(fde_ptr, 4 + length, Efault) {
+		/* Read PC begin (function start address) */
+		ret = read_encoded_pointer(sec, fde, &cur, end, fde->cie.fde_addr_enc, &func_addr);
+		if (ret)
+			return ret;
+		if (func_addr < sec->text_start || func_addr >= sec->text_end)
+			return -EINVAL;
+
+		/* Read PC range (function size) using PE format only */
+		u8 range_enc = DW_EH_PE_format(fde->cie.fde_addr_enc);
+		ret = read_encoded_pointer(sec, fde, &cur, end, range_enc, &func_size);
+		if (ret)
+			return ret;
+		if (func_addr + func_size > sec->text_end)
+			return -EINVAL;
+
+		/* Skip augmentation data if present */
+		if (fde->cie.aug_data_present) {
+			unsigned long aug_data_len;
+
+			ret = read_uleb128(&cur, end, &aug_data_len);
+			if (ret)
+				return ret;
+			if (cur + aug_data_len > end)
+				return -EINVAL;
+			cur += aug_data_len;
+		}
+	}
+
+	fde->func_addr		= func_addr;
+	fde->func_size		= func_size;
+	fde->cfi_insn_start	= cur;
+	fde->cfi_insn_end	= end;
+
+	return 0;
+
+Efault:
+	return -EFAULT;
+}
+
+
+static __always_inline int __find_fde(struct eh_frame_section *sec,
+				      unsigned long ip,
+				      struct eh_frame_fde *fde)
+{
+	void __user *table_start_ptr;
+	unsigned long table_size;
+	u8 table_enc;
+	int entry_size;
+	unsigned long low, high;
+	unsigned long found_cur = 0, found_func_addr;
+	unsigned long fde_addr;
+	int ret;
+
+	if (!sec->fde_count)
+		return -ENOENT;
+
+	table_enc = sec->binary_search_table_enc;
+	entry_size = 2 * encoded_pointer_size(table_enc);
+	if (!entry_size)
+		return -EINVAL;
+
+	table_start_ptr = (void __user *)sec->binary_search_table_start;
+	table_size = sec->binary_search_table_end - sec->binary_search_table_start;
+	scoped_user_read_access_size(table_start_ptr, table_size, Efault) {
+		/*
+		 * Binary search in .eh_frame_hdr table using half-open
+		 * interval [low, high) to avoid underflow of high if
+		 * target IP is lower than first entry.
+		 */
+		low = 0;
+		high = sec->fde_count;
+		while (low < high) {
+			unsigned long mid, cur, func_addr;
+
+			mid = low + ((high - low) / 2);
+			cur = sec->binary_search_table_start + mid * entry_size;
+
+			/* Read function start address from table */
+			ret = read_encoded_pointer(sec, NULL, &cur, sec->binary_search_table_end,
+						   table_enc, &func_addr);
+			if (ret)
+				return ret;
+
+			if (ip >= func_addr) {
+				found_cur = cur;
+				found_func_addr = func_addr;
+				low = mid + 1;
+			} else {
+				high = mid;
+			}
+		}
+
+		if (!found_cur)
+			return -ENOENT;
+
+		/* Read FDE address from table */
+		ret = read_encoded_pointer(sec, NULL, &found_cur, sec->binary_search_table_end,
+					   table_enc, &fde_addr);
+		if (ret)
+			return ret;
+		if (fde_addr < sec->eh_frame_start)
+			return -EINVAL;
+	}
+
+	ret = __read_fde(sec, fde_addr, fde);
+	if (ret)
+		return ret;
+	if (found_func_addr != fde->func_addr)
+		return -EINVAL;
+
+	/* Make sure it is not a gap */
+	if (ip < fde->func_addr || ip >= fde->func_addr + fde->func_size)
+		return -ENOENT;
+
+	return 0;
+
+Efault:
+	return -EFAULT;
+}
+
+/* Helper to convert DWARF register number to index (FP=0, RA=1) */
+static inline int reg_to_index(unsigned int reg)
+{
+	if (reg == EH_FRAME_REG_FP)
+		return 0;
+	if (reg == EH_FRAME_REG_RA)
+		return 1;
+	return -1;
+}
+
+static __always_inline int __do_cfi_insn(struct eh_frame_section *sec,
+					 struct eh_frame_fde *fde,
+					 unsigned long *cur_ptr,
+					 unsigned long end,
+					 unsigned long *ip_ptr,
+					 unsigned long target_ip,
+					 struct eh_frame_cfi_context *ctx)
+{
+	unsigned long cur = *cur_ptr;
+	unsigned long ip = *ip_ptr;
+	u8 opcode;
+	int ret;
+
+	UNSAFE_GET_USER_INC(opcode, cur, end, Efault);
+
+	switch (DW_CFA_opcode(opcode)) {
+	case DW_CFA_advance_loc: {
+		unsigned long offset = DW_CFA_operand(opcode) * fde->cie.code_align;
+
+		ip += offset;
+		break;
+	}
+
+	case DW_CFA_offset: {
+		u8 reg = DW_CFA_operand(opcode);
+		long offset;
+		int idx;
+
+		ret = read_uleb128(&cur, end, &offset);
+		if (ret)
+			return ret;
+		offset *= fde->cie.data_align;
+
+		if (reg == EH_FRAME_REG_SP && eh_frame_reject_sp_rule())
+			return -EOPNOTSUPP;
+
+		idx = reg_to_index(reg);
+		if (idx >= 0) {
+			ctx->state.reg_rule[idx] = REG_OFFSET;
+			ctx->state.reg_offset[idx] = offset;
+		}
+		break;
+	}
+
+	case DW_CFA_restore: {
+		u8 reg = DW_CFA_operand(opcode);
+		int idx;
+
+		idx = reg_to_index(reg);
+		if (idx >= 0)
+			ctx->state.reg_rule[idx] = REG_UNDEFINED_IMPLICIT;
+		break;
+	}
+
+	case 0: /* Extended opcodes */
+		switch (opcode) {
+		case DW_CFA_nop:
+			break;
+
+		case DW_CFA_advance_loc1: {
+			unsigned long offset;
+			u8 delta;
+
+			UNSAFE_GET_USER_INC(delta, cur, end, Efault);
+			offset = delta * fde->cie.code_align;
+			ip += offset;
+			break;
+		}
+
+		case DW_CFA_advance_loc2: {
+			unsigned long offset;
+			u16 delta;
+
+			UNSAFE_GET_USER_INC(delta, cur, end, Efault);
+			offset = delta * fde->cie.code_align;
+			ip += offset;
+			break;
+		}
+
+		case DW_CFA_advance_loc4: {
+			unsigned long offset;
+			u32 delta;
+
+			UNSAFE_GET_USER_INC(delta, cur, end, Efault);
+			offset = delta * fde->cie.code_align;
+			ip += offset;
+			break;
+		}
+
+		case DW_CFA_def_cfa: {
+			unsigned long reg, offset;
+
+			ret = read_uleb128(&cur, end, &reg);
+			if (ret)
+				return ret;
+			ret = read_uleb128(&cur, end, &offset);
+			if (ret)
+				return ret;
+
+			if (offset > LONG_MAX)
+				return -EOPNOTSUPP;
+
+			ctx->state.cfa_rule = CFA_REG_OFFSET;
+			ctx->state.cfa_regnum = reg;
+			ctx->state.cfa_offset = offset;
+			break;
+		}
+
+		case DW_CFA_def_cfa_sf: {
+			unsigned long reg;
+			long offset;
+
+			ret = read_uleb128(&cur, end, &reg);
+			if (ret)
+				return ret;
+			ret = read_sleb128(&cur, end, &offset);
+			if (ret)
+				return ret;
+			offset *= fde->cie.data_align;
+
+			ctx->state.cfa_rule = CFA_REG_OFFSET;
+			ctx->state.cfa_regnum = reg;
+			ctx->state.cfa_offset = offset;
+			break;
+		}
+
+		case DW_CFA_def_cfa_register: {
+			unsigned long reg;
+
+			ret = read_uleb128(&cur, end, &reg);
+			if (ret)
+				return ret;
+
+			ctx->state.cfa_rule = CFA_REG_OFFSET;
+			ctx->state.cfa_regnum = reg;
+			break;
+		}
+
+		case DW_CFA_def_cfa_offset: {
+			unsigned long offset;
+
+			ret = read_uleb128(&cur, end, &offset);
+			if (ret)
+				return ret;
+
+			if (offset > LONG_MAX)
+				return -EOPNOTSUPP;
+
+			ctx->state.cfa_offset = offset;
+			break;
+		}
+
+		case DW_CFA_def_cfa_offset_sf: {
+			long offset;
+
+			ret = read_sleb128(&cur, end, &offset);
+			if (ret)
+				return ret;
+			offset *= fde->cie.data_align;
+
+			ctx->state.cfa_offset = offset;
+			break;
+		}
+
+		case DW_CFA_undefined: {
+			unsigned long reg;
+			int idx;
+
+			ret = read_uleb128(&cur, end, &reg);
+			if (ret)
+				return ret;
+
+			if (reg == EH_FRAME_REG_SP)
+				return -EOPNOTSUPP;
+
+			idx = reg_to_index(reg);
+			if (idx >= 0)
+				ctx->state.reg_rule[idx] = REG_UNDEFINED_EXPLICIT;
+			break;
+		}
+
+		case DW_CFA_same_value: {
+			unsigned long reg;
+			int idx;
+
+			ret = read_uleb128(&cur, end, &reg);
+			if (ret)
+				return ret;
+
+			if (reg == EH_FRAME_REG_SP && eh_frame_reject_sp_rule())
+				return -EOPNOTSUPP;
+
+			idx = reg_to_index(reg);
+			if (idx >= 0)
+				ctx->state.reg_rule[idx] = REG_SAME_VALUE;
+			break;
+		}
+
+		case DW_CFA_offset_extended: {
+			unsigned long reg, _offset;
+			long offset;
+			int idx;
+
+			ret = read_uleb128(&cur, end, &reg);
+			if (ret)
+				return ret;
+			ret = read_uleb128(&cur, end, &_offset);
+			if (ret)
+				return ret;
+			offset = _offset * fde->cie.data_align;
+
+			if (reg == EH_FRAME_REG_SP && eh_frame_reject_sp_rule())
+				return -EOPNOTSUPP;
+
+			idx = reg_to_index(reg);
+			if (idx >= 0) {
+				ctx->state.reg_rule[idx] = REG_OFFSET;
+				ctx->state.reg_offset[idx] = offset;
+			}
+			break;
+		}
+
+		case DW_CFA_offset_extended_sf: {
+			unsigned long reg;
+			long offset;
+			int idx;
+
+			ret = read_uleb128(&cur, end, &reg);
+			if (ret)
+				return ret;
+			ret = read_sleb128(&cur, end, &offset);
+			if (ret)
+				return ret;
+			offset *= fde->cie.data_align;
+
+			if (reg == EH_FRAME_REG_SP && eh_frame_reject_sp_rule())
+				return -EOPNOTSUPP;
+
+			idx = reg_to_index(reg);
+			if (idx >= 0) {
+				ctx->state.reg_rule[idx] = REG_OFFSET;
+				ctx->state.reg_offset[idx] = offset;
+			}
+			break;
+		}
+
+		case DW_CFA_val_offset: {
+			unsigned long reg, _offset;
+			long offset;
+			int idx;
+
+			ret = read_uleb128(&cur, end, &reg);
+			if (ret)
+				return ret;
+			ret = read_uleb128(&cur, end, &_offset);
+			if (ret)
+				return ret;
+			offset = _offset * fde->cie.data_align;
+
+			if (reg == EH_FRAME_REG_SP && offset != EH_FRAME_SP_VAL_OFFSET)
+				return -EOPNOTSUPP;
+
+			idx = reg_to_index(reg);
+			if (idx >= 0) {
+				ctx->state.reg_rule[idx] = REG_VAL_OFFSET;
+				ctx->state.reg_offset[idx] = offset;
+			}
+			break;
+		}
+
+		case DW_CFA_val_offset_sf: {
+			unsigned long reg;
+			long offset;
+			int idx;
+
+			ret = read_uleb128(&cur, end, &reg);
+			if (ret)
+				return ret;
+			ret = read_sleb128(&cur, end, &offset);
+			if (ret)
+				return ret;
+			offset *= fde->cie.data_align;
+
+			if (reg == EH_FRAME_REG_SP && offset != EH_FRAME_SP_VAL_OFFSET)
+				return -EOPNOTSUPP;
+
+			idx = reg_to_index(reg);
+			if (idx >= 0) {
+				ctx->state.reg_rule[idx] = REG_VAL_OFFSET;
+				ctx->state.reg_offset[idx] = offset;
+			}
+			break;
+		}
+
+		case DW_CFA_register: {
+			unsigned long reg1, reg2;
+			int idx;
+
+			ret = read_uleb128(&cur, end, &reg1);
+			if (ret)
+				return ret;
+			ret = read_uleb128(&cur, end, &reg2);
+			if (ret)
+				return ret;
+
+			if (reg1 == EH_FRAME_REG_SP && eh_frame_reject_sp_rule())
+				return -EOPNOTSUPP;
+
+			idx = reg_to_index(reg1);
+			if (idx >= 0) {
+				ctx->state.reg_rule[idx] = REG_REGISTER;
+				ctx->state.reg_regnum[idx] = reg2;
+			}
+			break;
+		}
+
+		case DW_CFA_expression:
+		case DW_CFA_val_expression: {
+			unsigned long reg, expr_len;
+
+			ret = read_uleb128(&cur, end, &reg);
+			if (ret)
+				return ret;
+			ret = read_uleb128(&cur, end, &expr_len);
+			if (ret)
+				return ret;
+
+			if (cur + expr_len > end)
+				return -EINVAL;
+
+			if (reg == EH_FRAME_REG_SP || reg == EH_FRAME_REG_FP || reg == EH_FRAME_REG_RA)
+				return -EOPNOTSUPP;
+
+			cur += expr_len;
+			break;
+		}
+
+		case DW_CFA_remember_state:
+			if (ctx->stack_depth >= EH_FRAME_MAX_STATE_STACK)
+				return -EINVAL;
+			ctx->stack[ctx->stack_depth++] = ctx->state;
+			break;
+
+		case DW_CFA_restore_state:
+			if (ctx->stack_depth <= 0)
+				return -EINVAL;
+			ctx->state = ctx->stack[--ctx->stack_depth];
+			break;
+
+		default:
+			return -EOPNOTSUPP;
+		}
+		break;
+
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	*cur_ptr = cur;
+	*ip_ptr = ip;
+	return 0;
+
+Efault:
+	return -EFAULT;
+}
+
+static __always_inline int __do_cfi_program(struct eh_frame_section *sec,
+					    struct eh_frame_fde *fde,
+					    unsigned long target_ip,
+					    struct eh_frame_cfi_context *ctx)
+{
+	void __user *cfi_ptr;
+	unsigned long cfi_size;
+	unsigned long ip = fde->func_addr;
+	unsigned long cur;
+	int ret;
+
+	/* Initialize state */
+	ctx->state.cfa_rule = CFA_UNDEFINED;
+	ctx->state.reg_rule[FP_IDX] = REG_UNDEFINED_IMPLICIT;
+	ctx->state.reg_rule[RA_IDX] = REG_UNDEFINED_IMPLICIT;
+	ctx->stack_depth = 0;
+
+	/* Process CIE initial CFI instructions (if any) */
+	cfi_ptr = (void __user *)fde->cie.cfi_insn_start;
+	cfi_size = fde->cie.cfi_insn_end - fde->cie.cfi_insn_start;
+	scoped_user_read_access_size(cfi_ptr, cfi_size, Efault) {
+		cur = fde->cie.cfi_insn_start;
+		while (cur < fde->cie.cfi_insn_end) {
+			ret = __do_cfi_insn(sec, fde, &cur, fde->cie.cfi_insn_end, &ip, target_ip, ctx);
+			if (ret)
+				return ret;
+		}
+	}
+
+	/* Do not allow remember/restore between CIE and FDE */
+	ctx->stack_depth = 0;
+
+	/* Process FDE CFI instructions up to target IP */
+	cfi_ptr = (void __user *)fde->cfi_insn_start;
+	cfi_size = fde->cfi_insn_end - fde->cfi_insn_start;
+	scoped_user_read_access_size(cfi_ptr, cfi_size, Efault) {
+		cur = fde->cfi_insn_start;
+		while (cur < fde->cfi_insn_end && ip <= target_ip) {
+			ret = __do_cfi_insn(sec, fde, &cur, fde->cfi_insn_end, &ip, target_ip, ctx);
+			if (ret)
+				return ret;
+		}
+	}
+
+	return 0;
+
+Efault:
+	return -EFAULT;
+}
+
+static __always_inline int __find_frame_row(struct eh_frame_section *sec,
+					    struct eh_frame_fde *fde,
+					    unsigned long ip,
+					    struct unwind_user_frame *frame)
+{
+	struct eh_frame_cfi_context ctx;
+	int ret;
+
+	/* TODO: Signal frame - not supported yet */
+	if (fde->cie.signal_frame)
+		return -EOPNOTSUPP;
+
+	ret = __do_cfi_program(sec, fde, ip, &ctx);
+	if (ret)
+		return ret;
+
+	/* Convert CFA rule */
+	if (ctx.state.cfa_rule != CFA_REG_OFFSET)
+		return -EINVAL;
+
+	if (ctx.state.cfa_regnum == EH_FRAME_REG_SP)
+		frame->cfa.rule = UNWIND_USER_CFA_RULE_SP_OFFSET;
+	else if (ctx.state.cfa_regnum == EH_FRAME_REG_FP)
+		frame->cfa.rule = UNWIND_USER_CFA_RULE_FP_OFFSET;
+	else {
+		if (ctx.state.cfa_regnum > UINT_MAX)
+			return -EINVAL;
+		frame->cfa.rule = UNWIND_USER_CFA_RULE_REG_OFFSET;
+		frame->cfa.regnum = ctx.state.cfa_regnum;
+	}
+
+	if (ctx.state.cfa_offset < INT_MIN ||
+	    ctx.state.cfa_offset > INT_MAX)
+		return -EOPNOTSUPP;
+	frame->cfa.offset = ctx.state.cfa_offset;
+
+	/* Convert RA rule */
+	frame->outermost = false;
+	switch (ctx.state.reg_rule[RA_IDX]) {
+	case REG_UNDEFINED_IMPLICIT:
+		frame->ra.rule = UNWIND_USER_RULE_RETAIN;
+		break;
+	case REG_UNDEFINED_EXPLICIT:
+		frame->outermost = true;
+		break;
+	case REG_SAME_VALUE:
+		frame->ra.rule = UNWIND_USER_RULE_RETAIN;
+		break;
+	case REG_OFFSET:
+		if (ctx.state.reg_offset[RA_IDX] < INT_MIN ||
+		    ctx.state.reg_offset[RA_IDX] > INT_MAX)
+			return -EOPNOTSUPP;
+		frame->ra.rule = UNWIND_USER_RULE_CFA_OFFSET_DEREF;
+		frame->ra.offset = ctx.state.reg_offset[RA_IDX];
+		break;
+	case REG_VAL_OFFSET:
+		if (ctx.state.reg_offset[RA_IDX] < INT_MIN ||
+		    ctx.state.reg_offset[RA_IDX] > INT_MAX)
+			return -EOPNOTSUPP;
+		frame->ra.rule = UNWIND_USER_RULE_CFA_OFFSET;
+		frame->ra.offset = ctx.state.reg_offset[RA_IDX];
+		break;
+	case REG_REGISTER:
+		if (ctx.state.reg_regnum[RA_IDX] > UINT_MAX)
+			return -EINVAL;
+		frame->ra.rule = UNWIND_USER_RULE_REG_OFFSET;
+		frame->ra.regnum = ctx.state.reg_regnum[RA_IDX];
+		frame->ra.offset = 0;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* Convert FP rule */
+	switch (ctx.state.reg_rule[FP_IDX]) {
+	case REG_UNDEFINED_IMPLICIT:
+	case REG_UNDEFINED_EXPLICIT:
+		frame->fp.rule = UNWIND_USER_RULE_RETAIN;
+		break;
+	case REG_SAME_VALUE:
+		frame->fp.rule = UNWIND_USER_RULE_RETAIN;
+		break;
+	case REG_OFFSET:
+		if (ctx.state.reg_offset[FP_IDX] < INT_MIN ||
+		    ctx.state.reg_offset[FP_IDX] > INT_MAX)
+			return -EOPNOTSUPP;
+		frame->fp.rule = UNWIND_USER_RULE_CFA_OFFSET_DEREF;
+		frame->fp.offset = ctx.state.reg_offset[FP_IDX];
+		break;
+	case REG_VAL_OFFSET:
+		if (ctx.state.reg_offset[FP_IDX] < INT_MIN ||
+		    ctx.state.reg_offset[FP_IDX] > INT_MAX)
+			return -EOPNOTSUPP;
+		frame->fp.rule = UNWIND_USER_RULE_CFA_OFFSET;
+		frame->fp.offset = ctx.state.reg_offset[FP_IDX];
+		break;
+	case REG_REGISTER:
+		if (ctx.state.reg_regnum[FP_IDX] > UINT_MAX)
+			return -EINVAL;
+		frame->fp.rule = UNWIND_USER_RULE_REG_OFFSET;
+		frame->fp.regnum = ctx.state.reg_regnum[FP_IDX];
+		frame->fp.offset = 0;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* SP offset from CFA used in implicit CFA rule */
+	frame->sp_off = EH_FRAME_SP_VAL_OFFSET;
+
+	return 0;
+}
+
+int eh_frame_find(unsigned long ip, struct unwind_user_frame *frame)
+{
+	struct mm_struct *mm = current->mm;
+	struct eh_frame_section *sec;
+	struct eh_frame_fde fde;
+	int ret;
+
+	if (!mm)
+		return -EINVAL;
+
+	guard(srcu)(&eh_frame_srcu);
+
+	sec = mtree_load(&mm->eh_frame_mt, ip);
+	if (!sec)
+		return -ENOENT;
+
+	ret = __find_fde(sec, ip, &fde);
+	if (!ret)
+		ret = __find_frame_row(sec, &fde, ip, frame);
+
+	return ret;
+}
+
 static void free_section(struct eh_frame_section *sec)
 {
 	kfree(sec);
@@ -258,13 +1191,13 @@ static int eh_frame_read_header(struct eh_frame_section *sec)
 			return -EINVAL;
 
 		/* Read pointer to .eh_frame */
-		ret = read_encoded_pointer(sec, &cur, end,
+		ret = read_encoded_pointer(sec, NULL, &cur, end,
 					   eh_frame_ptr_enc, &eh_frame_start);
 		if (ret)
 			return ret;
 
 		/* Read FDE count */
-		ret = read_encoded_pointer(sec, &cur, end,
+		ret = read_encoded_pointer(sec, NULL, &cur, end,
 					   fde_count_enc, &fde_count);
 		if (ret)
 			return ret;
diff --git a/kernel/unwind/eh_frame.h b/kernel/unwind/eh_frame.h
index 77eda5376dfb..9a0c71102742 100644
--- a/kernel/unwind/eh_frame.h
+++ b/kernel/unwind/eh_frame.h
@@ -2,6 +2,38 @@
 #ifndef _EH_FRAME_H
 #define _EH_FRAME_H
 
+/* DWARF CFI opcodes */
+#define DW_CFA_advance_loc		0x40
+#define DW_CFA_offset			0x80
+#define DW_CFA_restore			0xc0
+#define DW_CFA_nop			0x00
+#define DW_CFA_set_loc			0x01
+#define DW_CFA_advance_loc1		0x02
+#define DW_CFA_advance_loc2		0x03
+#define DW_CFA_advance_loc4		0x04
+#define DW_CFA_offset_extended		0x05
+#define DW_CFA_restore_extended		0x06
+#define DW_CFA_undefined		0x07
+#define DW_CFA_same_value		0x08
+#define DW_CFA_register			0x09
+#define DW_CFA_remember_state		0x0a
+#define DW_CFA_restore_state		0x0b
+#define DW_CFA_def_cfa			0x0c
+#define DW_CFA_def_cfa_register		0x0d
+#define DW_CFA_def_cfa_offset		0x0e
+#define DW_CFA_def_cfa_expression	0x0f
+#define DW_CFA_expression		0x10
+#define DW_CFA_offset_extended_sf	0x11
+#define DW_CFA_def_cfa_sf		0x12
+#define DW_CFA_def_cfa_offset_sf	0x13
+#define DW_CFA_val_offset		0x14
+#define DW_CFA_val_offset_sf		0x15
+#define DW_CFA_val_expression		0x16
+
+/* Helpers for CFI opcodes */
+#define DW_CFA_opcode(insn)		((insn) & 0xc0)
+#define DW_CFA_operand(insn)		((insn) & 0x3f)
+
 /* DWARF exception header pointer encodings */
 #define DW_EH_PE_omit			0xff
 /* Formats */
@@ -27,4 +59,10 @@
 #define DW_EH_PE_format(encoding)	((encoding) & 0x0f)
 #define DW_EH_PE_application(encoding)	((encoding) & 0x70)
 
+/* CIE/FDE constants */
+#define EH_FRAME_CIE_ID			0
+#define EH_FRAME_DWARF64_LENGTH		0xffffffff
+#define EH_FRAME_CIE_MIN_LENGTH		13
+#define EH_FRAME_FDE_MIN_LENGTH		10
+
 #endif /* _EH_FRAME_H */
diff --git a/kernel/unwind/eh_frame_debug.h b/kernel/unwind/eh_frame_debug.h
new file mode 100644
index 000000000000..5a3e4e7f065a
--- /dev/null
+++ b/kernel/unwind/eh_frame_debug.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _EH_FRAME_DEBUG_H
+#define _EH_FRAME_DEBUG_H
+
+#include <linux/eh_frame.h>
+#include "eh_frame.h"
+
+#ifdef CONFIG_DYNAMIC_DEBUG
+
+#define dbg(fmt, ...)							\
+	pr_debug("%s (%d): " fmt, current->comm, current->pid, ##__VA_ARGS__)
+
+#else /* !CONFIG_DYNAMIC_DEBUG */
+
+#define dbg(args...)			no_printk(args)
+
+#endif /* !CONFIG_DYNAMIC_DEBUG */
+
+#endif /* _EH_FRAME_DEBUG_H */
-- 
2.53.0


  parent reply	other threads:[~2026-08-18 14:50 UTC|newest]

Thread overview: 51+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-18 14:49 [RFC PATCH v1 00/25] unwind_user: Implement .eh_frame handling Jens Remus
2026-08-18 14:49 ` [RFC PATCH v1 01/25] unwind_user: Add generic and arch-specific headers to MAINTAINERS Jens Remus
2026-08-18 14:49 ` [RFC PATCH v1 02/25] unwind_user: Stop when reaching an outermost frame Jens Remus
2026-08-18 14:56   ` sashiko-bot
2026-08-18 14:49 ` [RFC PATCH v1 03/25] unwind_user: Enable archs that pass RA in a register Jens Remus
2026-08-18 14:58   ` sashiko-bot
2026-08-18 14:49 ` [RFC PATCH v1 04/25] unwind_user: Flexible FP/RA recovery rules Jens Remus
2026-08-18 14:58   ` sashiko-bot
2026-08-18 14:49 ` [RFC PATCH v1 05/25] unwind_user: Flexible CFA " Jens Remus
2026-08-18 14:57   ` sashiko-bot
2026-08-18 14:49 ` [RFC PATCH v1 06/25] unwind_user: Enable archs that define CFA = SP_callsite + offset Jens Remus
2026-08-18 14:57   ` sashiko-bot
2026-08-18 14:49 ` [RFC PATCH v1 07/25] unwind_user/eh_frame: Add support for reading .eh_frame_hdr section Jens Remus
2026-08-18 15:02   ` sashiko-bot
2026-08-18 14:49 ` [RFC PATCH v1 08/25] unwind_user/eh_frame: Store .eh_frame_hdr section data in per-mm maple tree Jens Remus
2026-08-18 15:08   ` sashiko-bot
2026-08-18 14:49 ` Jens Remus [this message]
2026-08-18 15:05   ` [RFC PATCH v1 09/25] unwind_user/eh_frame: Add support for reading .eh_frame section sashiko-bot
2026-08-18 14:49 ` [RFC PATCH v1 10/25] unwind_user/eh_frame: Detect .eh_frame_hdr sections in executables Jens Remus
2026-08-18 15:18   ` sashiko-bot
2026-08-18 14:49 ` [RFC PATCH v1 11/25] unwind_user/eh_frame: Wire up unwind_user to eh_frame Jens Remus
2026-08-18 15:09   ` sashiko-bot
2026-08-18 14:49 ` [RFC PATCH v1 12/25] unwind_user/eh_frame: Remove .eh_frame[_hdr] section on detected corruption Jens Remus
2026-08-18 15:10   ` sashiko-bot
2026-08-18 14:49 ` [RFC PATCH v1 13/25] unwind_user/eh_frame: Show file name in debug output Jens Remus
2026-08-18 15:00   ` sashiko-bot
2026-08-18 14:49 ` [RFC PATCH v1 14/25] unwind_user/eh_frame: Add .eh_frame[_hdr] validation option Jens Remus
2026-08-18 15:08   ` sashiko-bot
2026-08-18 14:49 ` [RFC PATCH v1 15/25] unwind_user/eh_frame: Duplicate registered .eh_frame[_hdr] section data on clone/fork Jens Remus
2026-08-18 15:11   ` sashiko-bot
2026-08-18 14:49 ` [RFC PATCH v1 16/25] unwind_user/eh_frame: Add linear .eh_frame search fallback Jens Remus
2026-08-18 15:06   ` sashiko-bot
2026-08-18 14:49 ` [RFC PATCH v1 17/25] unwind_user/eh_frame: Ignore DW_CFA_GNU_args_size Jens Remus
2026-08-18 15:04   ` sashiko-bot
2026-08-18 14:49 ` [RFC PATCH v1 18/25] unwind_user/eh_frame: Add support for DWARF expressions Jens Remus
2026-08-18 15:13   ` sashiko-bot
2026-08-18 14:49 ` [RFC PATCH v1 19/25] x86/uaccess: Add unsafe_copy_from_user() implementation Jens Remus
2026-08-18 15:08   ` sashiko-bot
2026-08-18 14:49 ` [RFC PATCH v1 20/25] unwind_user/eh_frame/x86: Enable eh_frame unwinding on x86 Jens Remus
2026-08-18 15:04   ` sashiko-bot
2026-08-18 14:49 ` [RFC PATCH v1 21/25] unwind_user/eh_frame/x86: Handle PLT expressions Jens Remus
2026-08-18 15:10   ` sashiko-bot
2026-08-18 14:49 ` [RFC PATCH v1 22/25] unwind_user/eh_frame/x86: Handle DRAP expressions Jens Remus
2026-08-18 15:10   ` sashiko-bot
2026-08-18 14:49 ` [RFC PATCH v1 23/25] s390/ptrace: Provide frame_pointer() Jens Remus
2026-08-18 15:06   ` sashiko-bot
2026-08-18 14:49 ` [RFC PATCH v1 24/25] unwind_user/eh_frame/s390: Enable eh_frame unwinding on s390 Jens Remus
2026-08-18 15:15   ` sashiko-bot
2026-08-18 14:49 ` [RFC PATCH v1 25/25] unwind_user/eh_frame: Add prctl() interface for (un)registering .eh_frame_hdr sections Jens Remus
2026-08-18 15:17   ` sashiko-bot
2026-08-18 17:21 ` [RFC PATCH v1 00/25] unwind_user: Implement .eh_frame handling Steven Rostedt

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260818144954.2320378-10-jremus@linux.ibm.com \
    --to=jremus@linux.ibm.com \
    --cc=agordeev@linux.ibm.com \
    --cc=andrii@kernel.org \
    --cc=bp@alien8.de \
    --cc=dave.hansen@linux.intel.com \
    --cc=gor@linux.ibm.com \
    --cc=hca@linux.ibm.com \
    --cc=hpa@zytor.com \
    --cc=ibhagatgnu@gmail.com \
    --cc=iii@linux.ibm.com \
    --cc=jpoimboe@kernel.org \
    --cc=kees@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-s390@vger.kernel.org \
    --cc=linux-trace-kernel@vger.kernel.org \
    --cc=mathieu.desnoyers@efficios.com \
    --cc=mingo@redhat.com \
    --cc=namhyung@kernel.org \
    --cc=peterz@infradead.org \
    --cc=rostedt@kernel.org \
    --cc=sam@gentoo.org \
    --cc=tglx@kernel.org \
    --cc=x86@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.