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

[RFC PATCH v1 21/25] unwind_user/eh_frame/x86: Handle PLT expressions

From: Jens Remus <hidden>
Date: 2026-08-18 14:50:30
Also in: linux-s390, lkml
Subsystem: the rest, userspace stack unwinding, x86 architecture (32-bit and 64-bit), x86 stack unwinding · Maintainers: Linus Torvalds, Josh Poimboeuf, Steven Rostedt, Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, Peter Zijlstra

Add x86-specific support for handling the CFA expression found in GNU
linker generated .eh_frame for PLT.

This enables unwinding through GNU linker generated PLT entries on i386
and x86-64, unless linker option --no-ld-generated-unwind-info is used.

Signed-off-by: Jens Remus <redacted>
---
 arch/x86/include/asm/unwind_user_eh_frame.h | 68 ++++++++++++++++++++-
 kernel/unwind/eh_frame.c                    |  3 +-
 kernel/unwind/eh_frame.h                    | 14 +++++
 3 files changed, 83 insertions(+), 2 deletions(-)
diff --git a/arch/x86/include/asm/unwind_user_eh_frame.h b/arch/x86/include/asm/unwind_user_eh_frame.h
index fdccbda2fe4b..8268eb1a0ff5 100644
--- a/arch/x86/include/asm/unwind_user_eh_frame.h
+++ b/arch/x86/include/asm/unwind_user_eh_frame.h
@@ -2,6 +2,8 @@
 #ifndef _ASM_X86_UNWIND_USER_EH_FRAME_H
 #define _ASM_X86_UNWIND_USER_EH_FRAME_H
 
+#include <linux/unwind_user_eh_frame_types.h>
+
 #ifdef CONFIG_X86_64
 
 #define EH_FRAME_REG_SP	7	/* designated stack pointer register */
@@ -15,7 +17,71 @@
 #define EH_FRAME_MIN_DATA_ALIGN -8
 #define EH_FRAME_MAX_DATA_ALIGN -1
 
-#endif
+#endif /* CONFIG_X86_64 */
+
+static inline int memcmp_masked(const void *s1, const void *s2,
+				const void *mask, size_t n)
+{
+	const unsigned char *p1 = s1, *p2 = s2, *m = mask;
+	int res = 0;
+
+	while (n--)
+		if ((res = (*p1++ ^ *p2++) & *m++))
+			break;
+
+	return res;
+}
+
+static inline int eh_frame_do_def_cfa_expression(const char *expr,
+						 int size,
+						 unsigned long ip,
+						 struct eh_frame_reg_state *reg_state)
+{
+	/*
+	 * PLT CFA expression:
+	 *
+	 * DW_OP_breg<SP> + <SP_offset>	// 4 (ESP) + 4 or 7 (RSP) + 8
+	 * DW_OP_breg<IP> + 0		// 8 (EIP) or 16 (RIP)
+	 * DW_OP_lit15
+	 * DW_OP_and
+	 * DW_OP_lit<N>
+	 * DW_OP_ge
+	 * DW_OP_lit<shift>		// 2 or 3
+	 * DW_OP_shl
+	 * DW_OP_plus
+	 *
+	 * CFA = (SP + offset) + (((IP & 0xf) >= N) << shift)
+	 */
+	static const char plt_expr[] = {0x00,0x00,0x00,0x00,0x3f,0x1a,0x30,0x2a,0x30,0x24,0x22};
+	static const char plt_mask[] = {0x00,0xf0,0x00,0xff,0xff,0xff,0xf0,0xff,0xf0,0xff,0xff};
+
+	if (size == sizeof(plt_expr) &&
+	    !memcmp_masked(expr, plt_expr, plt_mask, sizeof(plt_expr))) {
+		unsigned char sp_op = expr[0];
+		unsigned char sp_offset = expr[1] & 0x0f;
+		unsigned char ip_op = expr[2];
+		unsigned char n = DW_OP_lit_value(expr[6]);
+		unsigned char shift = DW_OP_lit_value(expr[8]);
+		unsigned char sp_reg, ip_reg;
+
+		if (!DW_OP_is_breg(sp_op) || !DW_OP_is_breg(ip_op))
+			return -EOPNOTSUPP;
+
+		sp_reg = DW_OP_breg_register(sp_op);
+		ip_reg = DW_OP_breg_register(ip_op);
+		if (sp_reg != EH_FRAME_REG_SP || ip_reg != EH_FRAME_REG_RA)
+			return -EOPNOTSUPP;
+
+		/* CFA = (SP + SP_offset) + (((IP & 0xf) >= N) << shift) */
+		reg_state->cfa_rule = CFA_REG_OFFSET;
+		reg_state->cfa_regnum = EH_FRAME_REG_SP;
+		reg_state->cfa_offset = sp_offset + (((ip & 15) >= n) << shift);
+		return 0;
+	}
+
+	return -EOPNOTSUPP;
+}
+#define eh_frame_do_def_cfa_expression eh_frame_do_def_cfa_expression
 
 #include <asm-generic/unwind_user_eh_frame.h>
 
diff --git a/kernel/unwind/eh_frame.c b/kernel/unwind/eh_frame.c
index f7f1234b0437..19e2bc96ddbc 100644
--- a/kernel/unwind/eh_frame.c
+++ b/kernel/unwind/eh_frame.c
@@ -15,11 +15,12 @@
 #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"
 
+#include <asm/unwind_user_eh_frame.h>
+
 struct eh_frame_cfi_context {
 	struct eh_frame_reg_state state;
 	struct eh_frame_reg_state stack[EH_FRAME_MAX_STATE_STACK];
diff --git a/kernel/unwind/eh_frame.h b/kernel/unwind/eh_frame.h
index a43560486283..2f87d1b32bbc 100644
--- a/kernel/unwind/eh_frame.h
+++ b/kernel/unwind/eh_frame.h
@@ -60,6 +60,20 @@
 #define DW_EH_PE_format(encoding)	((encoding) & 0x0f)
 #define DW_EH_PE_application(encoding)	((encoding) & 0x70)
 
+/* DWARF expression operations */
+#define DW_OP_lit0	0x30
+/* ... */
+#define DW_OP_lit31	0x4f
+#define DW_OP_breg0	0x70
+/* ... */
+#define DW_OP_breg31	0x8f
+
+/* Helpers for DWARF expression operations */
+#define DW_OP_is_lit(op)		((op) >= DW_OP_lit0 && (op) <= DW_OP_lit31)
+#define DW_OP_is_breg(op)		((op) >= DW_OP_breg0 && (op) <= DW_OP_breg31)
+#define DW_OP_lit_value(op)		((op) - DW_OP_lit0)
+#define DW_OP_breg_register(op)		((op) - DW_OP_breg0)
+
 /* CIE/FDE constants */
 #define EH_FRAME_CIE_ID			0
 #define EH_FRAME_DWARF64_LENGTH		0xffffffff
-- 
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