diff --git a/arch/x86/include/asm/unwind_user_eh_frame.h b/arch/x86/include/asm/unwind_user_eh_frame.h
index 8268eb1a0ff5..670740cdbd8c 100644
--- a/arch/x86/include/asm/unwind_user_eh_frame.h
+++ b/arch/x86/include/asm/unwind_user_eh_frame.h
@@ -79,10 +79,75 @@ static inline int eh_frame_do_def_cfa_expression(const char *expr,
return 0;
}
+ /*
+ * DRAP (Dynamic Realignment Pointer) CFA expression:
+ *
+ * DW_OP_breg<FP> + <FP_offset> // 5 (EBP) - 4 or 6 (RBP) - 8
+ * DW_OP_deref
+ *
+ * CFA = *(FP + FP_offset)
+ */
+ static const char drap_expr[] = {0x70, 0x00, 0x06};
+ static const char drap_mask[] = {0xf0, 0x80, 0xff};
+
+ if (size == sizeof(drap_expr) &&
+ !memcmp_masked(expr, drap_expr, drap_mask, sizeof(drap_expr))) {
+ unsigned char fp_reg = DW_OP_breg_register(expr[0]);
+ unsigned char fp_offset_byte = expr[1];
+ long fp_offset;
+
+ if (fp_reg != EH_FRAME_REG_FP)
+ return -EOPNOTSUPP;
+
+ fp_offset = (long)(fp_offset_byte);
+ if (fp_offset_byte & 0x40)
+ fp_offset |= -(1L << 7); /* Sign extend */
+
+ /* CFA = *(FP + offset) */
+ reg_state->cfa_rule = CFA_REG_OFFSET_DEREF;
+ reg_state->cfa_regnum = EH_FRAME_REG_FP;
+ reg_state->cfa_offset = fp_offset;
+ return 0;
+ }
+
return -EOPNOTSUPP;
}
#define eh_frame_do_def_cfa_expression eh_frame_do_def_cfa_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)
+{
+ /*
+ * DRAP (Dynamic Realignment Pointer) FP expression:
+ *
+ * DW_OP_breg<FP> +0 // 5 (EBP) or 6 (RBP)
+ *
+ * FP = *(FP + 0)
+ */
+ static const char drap_fp_expr[] = {0x70, 0x00};
+ static const char drap_fp_mask[] = {0xf0, 0xff};
+
+ if (reg == EH_FRAME_REG_FP && size == sizeof(drap_fp_expr) &&
+ !memcmp_masked(expr, drap_fp_expr, drap_fp_mask, sizeof(drap_fp_expr))) {
+ unsigned char fp_reg = DW_OP_breg_register(expr[0]);
+
+ if (fp_reg != EH_FRAME_REG_FP)
+ return -EOPNOTSUPP;
+
+ /* FP = *(FP + 0) */
+ reg_state->reg_rule[FP_IDX] = REG_REGISTER_OFFSET_DEREF;
+ reg_state->reg_regnum[FP_IDX] = EH_FRAME_REG_FP;
+ reg_state->reg_offset[FP_IDX] = 0;
+ return 0;
+ }
+
+ return -EOPNOTSUPP;
+}
+#define eh_frame_do_expression eh_frame_do_expression
+
#include <asm-generic/unwind_user_eh_frame.h>
#endif /* _ASM_X86_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
index e9f9d1abb76f..9547e963bb5f 100644
--- a/include/linux/unwind_user_eh_frame_types.h
+++ b/include/linux/unwind_user_eh_frame_types.h
@@ -5,6 +5,8 @@
enum eh_frame_cfa_rule {
CFA_UNDEFINED, /* unrecoverable */
CFA_REG_OFFSET, /* CFA = reg + offset */
+ /* CFA expressions rules */
+ CFA_REG_OFFSET_DEREF, /* CFA = *(reg + offset) */
};
enum eh_frame_reg_rule {@@ -14,6 +16,8 @@ enum eh_frame_reg_rule {
REG_OFFSET, /* reg = *(CFA + offset) */
REG_VAL_OFFSET, /* reg = CFA + offset */
REG_REGISTER, /* reg = other_reg */
+ /* expressions rules */
+ REG_REGISTER_OFFSET_DEREF, /* reg = *(other_reg + offset) */
};
enum eh_frame_reg_index {diff --git a/kernel/unwind/eh_frame.c b/kernel/unwind/eh_frame.c
index 0a81ba1a0377..d1656f5d40f1 100644
--- a/kernel/unwind/eh_frame.c
+++ b/kernel/unwind/eh_frame.c
@@ -1098,18 +1098,33 @@ static __always_inline int __find_frame_row(struct eh_frame_section *sec,
return ret;
/* Convert CFA rule */
- if (ctx.state.cfa_rule != CFA_REG_OFFSET)
+ switch (ctx.state.cfa_rule) {
+ case CFA_REG_OFFSET:
+ 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;
+ }
+ break;
+ case CFA_REG_OFFSET_DEREF:
+ if (ctx.state.cfa_regnum == EH_FRAME_REG_SP)
+ frame->cfa.rule = UNWIND_USER_CFA_RULE_SP_OFFSET_DEREF;
+ else if (ctx.state.cfa_regnum == EH_FRAME_REG_FP)
+ frame->cfa.rule = UNWIND_USER_CFA_RULE_FP_OFFSET_DEREF;
+ else {
+ if (ctx.state.cfa_regnum > UINT_MAX)
+ return -EINVAL;
+ frame->cfa.rule = UNWIND_USER_CFA_RULE_REG_OFFSET_DEREF;
+ frame->cfa.regnum = ctx.state.cfa_regnum;
+ }
+ break;
+ default:
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 ||@@ -1150,6 +1165,16 @@ static __always_inline int __find_frame_row(struct eh_frame_section *sec,
frame->ra.regnum = ctx.state.reg_regnum[RA_IDX];
frame->ra.offset = 0;
break;
+ case REG_REGISTER_OFFSET_DEREF:
+ if (ctx.state.reg_regnum[RA_IDX] > UINT_MAX)
+ return -EINVAL;
+ 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_REG_OFFSET_DEREF;
+ frame->ra.regnum = ctx.state.reg_regnum[RA_IDX];
+ frame->ra.offset = ctx.state.reg_offset[RA_IDX];
+ break;
default:
return -EINVAL;
}
@@ -1184,6 +1209,16 @@ static __always_inline int __find_frame_row(struct eh_frame_section *sec,
frame->fp.regnum = ctx.state.reg_regnum[FP_IDX];
frame->fp.offset = 0;
break;
+ case REG_REGISTER_OFFSET_DEREF:
+ if (ctx.state.reg_regnum[FP_IDX] > UINT_MAX)
+ return -EINVAL;
+ 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_REG_OFFSET_DEREF;
+ frame->fp.regnum = ctx.state.reg_regnum[FP_IDX];
+ frame->fp.offset = ctx.state.reg_offset[FP_IDX];
+ break;
default:
return -EINVAL;
}
--
2.53.0