Thread (67 messages) flat view 67 messages, 3 authors, 2026-08-21
COLD31d

[RFC PATCH v1 18/25] unwind_user/eh_frame: Add support for DWARF expressions

From: Jens Remus <hidden>
Date: 2026-08-18 14:50:25
Also in: linux-s390, lkml
Subsystem: generic include/asm header files, the rest, userspace stack unwinding · Maintainers: Arnd Bergmann, Linus Torvalds, Josh Poimboeuf, Steven Rostedt

Enable architectures to handle DWARF expressions in DWARF CFI
instructions DW_CFA_def_cfa_expression, DW_CFA_expression, and
DW_CFA_val_expression.  Limit the maximum expression length to a
reasonable size, while enabling architectures to override the
limit.

Architectures are supposed to only handle specific known expressions
or expression patterns, not to implement a stack-based expression
evaluation machinery.

Signed-off-by: Jens Remus <redacted>
---
 include/asm-generic/unwind_user_eh_frame.h | 41 +++++++++++++
 include/linux/unwind_user_eh_frame_types.h | 37 ++++++++++++
 kernel/unwind/eh_frame.c                   | 70 +++++++++++-----------
 3 files changed, 113 insertions(+), 35 deletions(-)
 create mode 100644 include/linux/unwind_user_eh_frame_types.h
diff --git a/include/asm-generic/unwind_user_eh_frame.h b/include/asm-generic/unwind_user_eh_frame.h
index e6d207597206..da9bc52a645a 100644
--- a/include/asm-generic/unwind_user_eh_frame.h
+++ b/include/asm-generic/unwind_user_eh_frame.h
@@ -2,6 +2,8 @@
 #ifndef _ASM_GENERIC_UNWIND_USER_EH_FRAME_H
 #define _ASM_GENERIC_UNWIND_USER_EH_FRAME_H
 
+#include <linux/unwind_user_eh_frame_types.h>
+
 #ifndef EH_FRAME_MAX_CIE_LENGTH
 #define EH_FRAME_MAX_CIE_LENGTH 128
 #endif
@@ -10,6 +12,10 @@
 #define EH_FRAME_MAX_AUGSTR_LENGTH 16
 #endif
 
+#ifndef EH_FRAME_MAX_EXPRESSION_LENGTH
+#define EH_FRAME_MAX_EXPRESSION_LENGTH 32
+#endif
+
 #ifndef EH_FRAME_MAX_STATE_STACK
 #define EH_FRAME_MAX_STATE_STACK 8
 #endif
@@ -39,5 +45,40 @@ static inline bool eh_frame_reject_sp_rule(void)
 #define eh_frame_reject_sp_rule eh_frame_reject_sp_rule
 #endif
 
+#ifndef eh_frame_do_def_cfa_expression
+static inline int eh_frame_do_def_cfa_expression(const char *expr,
+						 int size,
+						 unsigned long ip,
+						 struct eh_frame_reg_state *reg_state)
+{
+	return -EOPNOTSUPP;
+}
+#define eh_frame_do_def_cfa_expression eh_frame_do_def_cfa_expression
+#endif
+
+#ifndef eh_frame_do_expression
+static inline int eh_frame_do_expression(unsigned int reg,
+					 const char *expr,
+					 int size,
+					 unsigned long ip,
+					 struct eh_frame_reg_state *reg_state)
+{
+	return -EOPNOTSUPP;
+}
+#define eh_frame_do_expression eh_frame_do_expression
+#endif
+
+#ifndef eh_frame_do_val_expression
+static inline int eh_frame_do_val_expression(unsigned int reg,
+					     const char *expr,
+					     int size,
+					     unsigned long ip,
+					     struct eh_frame_reg_state *reg_state)
+{
+	return -EOPNOTSUPP;
+}
+#define eh_frame_do_val_expression eh_frame_do_val_expression
+#endif
+
 #endif /* _ASM_GENERIC_UNWIND_USER_EH_FRAME_H */
 
diff --git a/include/linux/unwind_user_eh_frame_types.h b/include/linux/unwind_user_eh_frame_types.h
new file mode 100644
index 000000000000..e9f9d1abb76f
--- /dev/null
+++ b/include/linux/unwind_user_eh_frame_types.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_UNWIND_USER_EH_FRAME_TYPES_H
+#define _LINUX_UNWIND_USER_EH_FRAME_TYPES_H
+
+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];
+};
+
+#endif /* _LINUX_UNWIND_USER_EH_FRAME_TYPES_H */
diff --git a/kernel/unwind/eh_frame.c b/kernel/unwind/eh_frame.c
index 64176242b7d8..f7f1234b0437 100644
--- a/kernel/unwind/eh_frame.c
+++ b/kernel/unwind/eh_frame.c
@@ -12,45 +12,14 @@
 #include <linux/mm.h>
 #include <linux/string_helpers.h>
 #include <linux/eh_frame.h>
+#include <linux/types.h>
 #include <linux/unwind_user_types.h>
+#include <linux/unwind_user_eh_frame_types.h>
 #include <asm/unwind_user_eh_frame.h>
 
 #include "eh_frame.h"
 #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];
@@ -839,6 +808,27 @@ static __always_inline int __do_cfi_insn(struct eh_frame_section *sec,
 			break;
 		}
 
+		case DW_CFA_def_cfa_expression: {
+			unsigned long expr_len;
+			char expr[EH_FRAME_MAX_EXPRESSION_LENGTH];
+
+			ret = read_uleb128(&cur, end, &expr_len);
+			if (ret)
+				return ret;
+
+			if (cur + expr_len > end)
+				return -EINVAL;
+
+			if (expr_len > sizeof(expr))
+				return -EOPNOTSUPP;
+			unsafe_copy_from_user(&expr, (void __user *)cur, expr_len, Efault);
+			ret = eh_frame_do_def_cfa_expression(expr, expr_len, target_ip, &ctx->state);
+			if (ret)
+				return ret;
+			cur += expr_len;
+			break;
+		}
+
 		case DW_CFA_undefined: {
 			unsigned long reg;
 			int idx;
@@ -1005,9 +995,19 @@ static __always_inline int __do_cfi_insn(struct eh_frame_section *sec,
 			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;
+			if (reg == EH_FRAME_REG_SP || reg == EH_FRAME_REG_FP || reg == EH_FRAME_REG_RA) {
+				char expr[EH_FRAME_MAX_EXPRESSION_LENGTH];
 
+				if (expr_len > sizeof(expr))
+					return -EOPNOTSUPP;
+				unsafe_copy_from_user(&expr, (void __user *)cur, expr_len, Efault);
+				if (opcode == DW_CFA_expression)
+					ret = eh_frame_do_expression(reg, expr, expr_len, target_ip, &ctx->state);
+				else
+					ret = eh_frame_do_val_expression(reg, expr, expr_len, target_ip, &ctx->state);
+				if (ret)
+					return ret;
+			}
 			cur += expr_len;
 			break;
 		}
-- 
2.53.0
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help