[RFC PATCH v2 20/34] x86/extable: Implement EX_TYPE_FUNC_REWIND

Xin Li (Intel)
2025-04-22T08:23:48Z

From: "H. Peter Anvin (Intel)" <hpa@zytor.com>

Add a new exception type, which allows emulating an exception as if it
had happened at or near the call site of a function. This allows a
function call inside an alternative for instruction emulation to "kick
back" the exception into the alternatives pattern, possibly invoking a
different exception handling pattern there, or at least indicating the
"real" location of the fault.

Signed-off-by: H. Peter Anvin (Intel) <hpa@zytor.com>
Signed-off-by: Xin Li (Intel) <xin@zytor.com>
---
arch/x86/include/asm/asm.h | 6 +
arch/x86/include/asm/extable_fixup_types.h | 1 +
arch/x86/mm/extable.c | 135 +++++++++++++--------
3 files changed, 91 insertions(+), 51 deletions(-)

diff --git a/arch/x86/include/asm/asm.h b/arch/x86/include/asm/asm.h
index a9f07799e337..722340d7c1af 100644
--- a/arch/x86/include/asm/asm.h
+++ b/arch/x86/include/asm/asm.h
@@ -243,5 +243,11 @@ register unsigned long current_stack_pointer asm(_ASM_SP);
#define _ASM_EXTABLE_FAULT(from, to) \
_ASM_EXTABLE_TYPE(from, to, EX_TYPE_FAULT)

+#define _ASM_EXTABLE_FUNC_REWIND(from, ipdelta, spdelta) \
+ _ASM_EXTABLE_TYPE(from, from /* unused */, \
+ EX_TYPE_FUNC_REWIND | \
+ EX_DATA_REG(spdelta) | \
+ EX_DATA_IMM(ipdelta))
+
#endif /* __KERNEL__ */
#endif /* _ASM_X86_ASM_H */
diff --git a/arch/x86/include/asm/extable_fixup_types.h b/arch/x86/include/asm/extable_fixup_types.h
index 906b0d5541e8..9cd1cea45052 100644
--- a/arch/x86/include/asm/extable_fixup_types.h
+++ b/arch/x86/include/asm/extable_fixup_types.h
@@ -67,5 +67,6 @@
#define EX_TYPE_ZEROPAD 20 /* longword load with zeropad on fault */

#define EX_TYPE_ERETU 21
+#define EX_TYPE_FUNC_REWIND 22

#endif
diff --git a/arch/x86/mm/extable.c b/arch/x86/mm/extable.c
index f1743babafc8..6bf4c2a43c2c 100644
--- a/arch/x86/mm/extable.c
+++ b/arch/x86/mm/extable.c
@@ -319,6 +319,27 @@ static bool ex_handler_eretu(const struct exception_table_entry *fixup,
}
#endif

+/*
+ * Emulate a fault taken at the call site of a function.
+ *
+ * The combined reg and flags field are used as an unsigned number of
+ * machine words to pop off the stack before the return address, then
+ * the signed imm field is used as a delta from the return IP address.
+ */
+static bool ex_handler_func_rewind(struct pt_regs *regs, int data)
+{
+ const long ipdelta = FIELD_GET(EX_DATA_IMM_MASK, data);
+ const unsigned long pops = FIELD_GET(EX_DATA_REG_MASK | EX_DATA_FLAG_MASK, data);
+ unsigned long *sp;
+
+ sp = (unsigned long *)regs->sp;
+ sp += pops;
+ regs->ip = *sp++ + ipdelta;
+ regs->sp = (unsigned long)sp;
+
+ return true;
+}
+
int ex_get_fixup_type(unsigned long ip)
{
const struct exception_table_entry *e = search_exception_tables(ip);
@@ -331,6 +352,7 @@ int fixup_exception(struct pt_regs *regs, int trapnr, unsigned long error_code,
{
const struct exception_table_entry *e;
int type, reg, imm;
+ bool again;

#ifdef CONFIG_PNPBIOS
if (unlikely(SEGMENT_IS_PNP_CODE(regs->cs))) {
@@ -346,60 +368,71 @@ int fixup_exception(struct pt_regs *regs, int trapnr, unsigned long error_code,
}
#endif

- e = search_exception_tables(regs->ip);
- if (!e)
- return 0;
-
- type = FIELD_GET(EX_DATA_TYPE_MASK, e->data);
- reg = FIELD_GET(EX_DATA_REG_MASK, e->data);
- imm = FIELD_GET(EX_DATA_IMM_MASK, e->data);
-
- switch (type) {
- case EX_TYPE_DEFAULT:
- case EX_TYPE_DEFAULT_MCE_SAFE:
- return ex_handler_default(e, regs);
- case EX_TYPE_FAULT:
- case EX_TYPE_FAULT_MCE_SAFE:
- return ex_handler_fault(e, regs, trapnr);
- case EX_TYPE_UACCESS:
- return ex_handler_uaccess(e, regs, trapnr, fault_addr);
- case EX_TYPE_CLEAR_FS:
- return ex_handler_clear_fs(e, regs);
- case EX_TYPE_FPU_RESTORE:
- return ex_handler_fprestore(e, regs);
- case EX_TYPE_BPF:
- return ex_handler_bpf(e, regs);
- case EX_TYPE_WRMSR:
- return ex_handler_msr(e, regs, true, false, reg);
- case EX_TYPE_RDMSR:
- return ex_handler_msr(e, regs, false, false, reg);
- case EX_TYPE_WRMSR_SAFE:
- return ex_handler_msr(e, regs, true, true, reg);
- case EX_TYPE_RDMSR_SAFE:
- return ex_handler_msr(e, regs, false, true, reg);
- case EX_TYPE_WRMSR_IN_MCE:
- ex_handler_msr_mce(regs, true);
- break;
- case EX_TYPE_RDMSR_IN_MCE:
- ex_handler_msr_mce(regs, false);
- break;
- case EX_TYPE_POP_REG:
- regs->sp += sizeof(long);
- fallthrough;
- case EX_TYPE_IMM_REG:
- return ex_handler_imm_reg(e, regs, reg, imm);
- case EX_TYPE_FAULT_SGX:
- return ex_handler_sgx(e, regs, trapnr);
- case EX_TYPE_UCOPY_LEN:
- return ex_handler_ucopy_len(e, regs, trapnr, fault_addr, reg, imm);
- case EX_TYPE_ZEROPAD:
- return ex_handler_zeropad(e, regs, fault_addr);
+ do {
+ e = search_exception_tables(regs->ip);
+ if (!e)
+ return 0;
+
+ again = false;
+
+ type = FIELD_GET(EX_DATA_TYPE_MASK, e->data);
+ reg = FIELD_GET(EX_DATA_REG_MASK, e->data);
+ imm = FIELD_GET(EX_DATA_IMM_MASK, e->data);
+
+ switch (type) {
+ case EX_TYPE_DEFAULT:
+ case EX_TYPE_DEFAULT_MCE_SAFE:
+ return ex_handler_default(e, regs);
+ case EX_TYPE_FAULT:
+ case EX_TYPE_FAULT_MCE_SAFE:
+ return ex_handler_fault(e, regs, trapnr);
+ case EX_TYPE_UACCESS:
+ return ex_handler_uaccess(e, regs, trapnr, fault_addr);
+ case EX_TYPE_CLEAR_FS:
+ return ex_handler_clear_fs(e, regs);
+ case EX_TYPE_FPU_RESTORE:
+ return ex_handler_fprestore(e, regs);
+ case EX_TYPE_BPF:
+ return ex_handler_bpf(e, regs);
+ case EX_TYPE_WRMSR:
+ return ex_handler_msr(e, regs, true, false, reg);
+ case EX_TYPE_RDMSR:
+ return ex_handler_msr(e, regs, false, false, reg);
+ case EX_TYPE_WRMSR_SAFE:
+ return ex_handler_msr(e, regs, true, true, reg);
+ case EX_TYPE_RDMSR_SAFE:
+ return ex_handler_msr(e, regs, false, true, reg);
+ case EX_TYPE_WRMSR_IN_MCE:
+ ex_handler_msr_mce(regs, true);
+ break;
+ case EX_TYPE_RDMSR_IN_MCE:
+ ex_handler_msr_mce(regs, false);
+ break;
+ case EX_TYPE_POP_REG:
+ regs->sp += sizeof(long);
+ fallthrough;
+ case EX_TYPE_IMM_REG:
+ return ex_handler_imm_reg(e, regs, reg, imm);
+ case EX_TYPE_FAULT_SGX:
+ return ex_handler_sgx(e, regs, trapnr);
+ case EX_TYPE_UCOPY_LEN:
+ return ex_handler_ucopy_len(e, regs, trapnr, fault_addr, reg, imm);
+ case EX_TYPE_ZEROPAD:
+ return ex_handler_zeropad(e, regs, fault_addr);
#ifdef CONFIG_X86_FRED
- case EX_TYPE_ERETU:
- return ex_handler_eretu(e, regs, error_code);
+ case EX_TYPE_ERETU:
+ return ex_handler_eretu(e, regs, error_code);
#endif
- }
+ case EX_TYPE_FUNC_REWIND:
+ again = ex_handler_func_rewind(regs, e->data);
+ break;
+ default:
+ break; /* Will BUG() */
+ }
+ } while (again);
+
BUG();
+ return 0;
}

extern unsigned int early_recursion_flag;
--
2.49.0