diff --git a/arch/x86/kernel/unwind_orc.c b/arch/x86/kernel/unwind_orc.c index d557a545f4bc5552dfb22ec7ed174c102c678f6e..055a9df84de461f3536341f1613fa8248da66e30 100644 --- a/arch/x86/kernel/unwind_orc.c +++ b/arch/x86/kernel/unwind_orc.c @@ -146,6 +146,34 @@ static struct orc_entry orc_fp_entry = { .end = 0, }; +#ifdef CONFIG_PARAVIRT_XXL +static bool check_paravirt(struct unwind_state *state, struct orc_entry *orc) +{ + u8 *ip = (u8 *)state->ip; + + /* + * In paravirt_patch.c, patched paravirt opcode should be: + * pushfq; popq %rax // 0x9c 0x58 + * pushq %rdi; popfq // 0x57 0x9d + * + * Error unwinding only happens when: + * 1. In irq or preempt context. + * 2. Current insn is popq, and it doesn't change orc. + * 3. Last insn doesn't change orc, checking it first to + * promise ip - 1 is valid. + * 4. Last byte fits pushf. + */ + if (state->regs && orc->type == UNWIND_HINT_TYPE_CALL && + (ip[0] == 0x58 || ip[0] == 0x9d) && + orc == orc_find((unsigned long)(ip + 1)) && + orc == orc_find((unsigned long)(ip - 1)) && + (ip[-1] == 0x9c || ip[-1] == 0x57)) + return true; + + return false; +} +#endif + static struct orc_entry *orc_find(unsigned long ip) { static struct orc_entry *orc; @@ -425,6 +453,9 @@ bool unwind_next_frame(struct unwind_state *state) enum stack_type prev_type = state->stack_info.type; struct orc_entry *orc; bool indirect = false; +#ifdef CONFIG_PARAVIRT_XXL + struct orc_entry para_orc; +#endif if (unwind_done(state)) return false; @@ -457,6 +488,18 @@ bool unwind_next_frame(struct unwind_state *state) state->error = true; } +#ifdef CONFIG_PARAVIRT_XXL + /* + * When hitting paravirt POP insn, the orc entry should add + * one slot for PUSH insn. + */ + if (!state->error && check_paravirt(state, orc)) { + para_orc = *orc; + para_orc.sp_offset += sizeof(long); + orc = ¶_orc; + } +#endif + /* End-of-stack check for kernel threads: */ if (orc->sp_reg == ORC_REG_UNDEFINED) { if (!orc->end)