[RFC PATCH v1 18/25] unwind_user/eh_frame: Add support for DWARF expressions
From: Jens Remus
Date: Tue Aug 18 2026 - 10:51:51 EST
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 <jremus@xxxxxxxxxxxxx>
---
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