On Sun, 6 Dec 2020 23:11:38 +0900 Masami Hiramatsu <mhira...@kernel.org> wrote:
> Currently kprobes x86 decodes opcode right after single > stepping in resume_execution(). But it already decoded the > opcode while preparing arch_specific_insn in arch_copy_kprobe(). > > This decodes opcode in arch_copy_kprobe() instead of > resume_execution() and sets some flags which classifies > the opcode for resuming process. Acked-by: Steven Rostedt (VMware) <rost...@goodmis.org> This probably should go via the tip tree. -- Steve > > Signed-off-by: Masami Hiramatsu <mhira...@kernel.org> > --- > arch/x86/include/asm/kprobes.h | 11 ++- > arch/x86/kernel/kprobes/core.c | 166 > ++++++++++++++++++---------------------- > 2 files changed, 80 insertions(+), 97 deletions(-) > > diff --git a/arch/x86/include/asm/kprobes.h b/arch/x86/include/asm/kprobes.h > index 991a7ad540c7..d20a3d6be36e 100644 > --- a/arch/x86/include/asm/kprobes.h > +++ b/arch/x86/include/asm/kprobes.h > @@ -58,14 +58,17 @@ struct arch_specific_insn { > /* copy of the original instruction */ > kprobe_opcode_t *insn; > /* > - * boostable = false: This instruction type is not boostable. > - * boostable = true: This instruction has been boosted: we have > + * boostable = 0: This instruction type is not boostable. > + * boostable = 1: This instruction has been boosted: we have > * added a relative jump after the instruction copy in insn, > * so no single-step and fixup are needed (unless there's > * a post_handler). > */ > - bool boostable; > - bool if_modifier; > + unsigned boostable:1; > + unsigned if_modifier:1; > + unsigned is_call:1; > + unsigned is_pushf:1; > + unsigned is_abs_ip:1; > /* Number of bytes of text poked */ > int tp_len; > }; > diff --git a/arch/x86/kernel/kprobes/core.c b/arch/x86/kernel/kprobes/core.c > index 547c7abb39f5..9d95f43363f1 100644 > --- a/arch/x86/kernel/kprobes/core.c > +++ b/arch/x86/kernel/kprobes/core.c > @@ -132,26 +132,6 @@ void synthesize_relcall(void *dest, void *from, void *to) > } > NOKPROBE_SYMBOL(synthesize_relcall); > > -/* > - * Skip the prefixes of the instruction. > - */ > -static kprobe_opcode_t *skip_prefixes(kprobe_opcode_t *insn) > -{ > - insn_attr_t attr; > - > - attr = inat_get_opcode_attribute((insn_byte_t)*insn); > - while (inat_is_legacy_prefix(attr)) { > - insn++; > - attr = inat_get_opcode_attribute((insn_byte_t)*insn); > - } > -#ifdef CONFIG_X86_64 > - if (inat_is_rex_prefix(attr)) > - insn++; > -#endif > - return insn; > -} > -NOKPROBE_SYMBOL(skip_prefixes); > - > /* > * Returns non-zero if INSN is boostable. > * RIP relative instructions are adjusted at copying time in 64 bits mode > @@ -311,25 +291,6 @@ static int can_probe(unsigned long paddr) > return (addr == paddr); > } > > -/* > - * Returns non-zero if opcode modifies the interrupt flag. > - */ > -static int is_IF_modifier(kprobe_opcode_t *insn) > -{ > - /* Skip prefixes */ > - insn = skip_prefixes(insn); > - > - switch (*insn) { > - case 0xfa: /* cli */ > - case 0xfb: /* sti */ > - case 0xcf: /* iret/iretd */ > - case 0x9d: /* popf/popfd */ > - return 1; > - } > - > - return 0; > -} > - > /* > * Copy an instruction with recovering modified instruction by kprobes > * and adjust the displacement if the instruction uses the %rip-relative > @@ -411,9 +372,9 @@ static int prepare_boost(kprobe_opcode_t *buf, struct > kprobe *p, > synthesize_reljump(buf + len, p->ainsn.insn + len, > p->addr + insn->length); > len += JMP32_INSN_SIZE; > - p->ainsn.boostable = true; > + p->ainsn.boostable = 1; > } else { > - p->ainsn.boostable = false; > + p->ainsn.boostable = 0; > } > > return len; > @@ -450,12 +411,75 @@ void free_insn_page(void *page) > module_memfree(page); > } > > +static void set_resume_flags(struct kprobe *p, struct insn *insn) > +{ > + insn_byte_t opcode = insn->opcode.bytes[0]; > + > + switch (opcode) { > + case 0xfa: /* cli */ > + case 0xfb: /* sti */ > + case 0x9d: /* popf/popfd */ > + /* Check whether the instruction modifies Interrupt Flag or not > */ > + p->ainsn.if_modifier = 1; > + break; > + case 0x9c: /* pushfl */ > + p->ainsn.is_pushf = 1; > + break; > + case 0xcf: /* iret */ > + p->ainsn.if_modifier = 1; > + fallthrough; > + case 0xc2: /* ret/lret */ > + case 0xc3: > + case 0xca: > + case 0xcb: > + case 0xea: /* jmp absolute -- ip is correct */ > + /* ip is already adjusted, no more changes required */ > + p->ainsn.is_abs_ip = 1; > + /* Without resume jump, this is boostable */ > + p->ainsn.boostable = 1; > + break; > + case 0xe8: /* call relative - Fix return addr */ > + p->ainsn.is_call = 1; > + break; > +#ifdef CONFIG_X86_32 > + case 0x9a: /* call absolute -- same as call absolute, indirect */ > + p->ainsn.is_call = 1; > + p->ainsn.is_abs_ip = 1; > + break; > +#endif > + case 0xff: > + opcode = insn->opcode.bytes[1]; > + if ((opcode & 0x30) == 0x10) { > + /* > + * call absolute, indirect > + * Fix return addr; ip is correct. > + * But this is not boostable > + */ > + p->ainsn.is_call = 1; > + p->ainsn.is_abs_ip = 1; > + break; > + } else if (((opcode & 0x31) == 0x20) || > + ((opcode & 0x31) == 0x21)) { > + /* > + * jmp near and far, absolute indirect > + * ip is correct. > + */ > + p->ainsn.is_abs_ip = 1; > + /* Without resume jump, this is boostable */ > + p->ainsn.boostable = 1; > + } > + break; > + } > +} > + > static int arch_copy_kprobe(struct kprobe *p) > { > struct insn insn; > kprobe_opcode_t buf[MAX_INSN_SIZE]; > int len; > > + memset(&p->ainsn, 0, sizeof(p->ainsn)); > + > /* Copy an instruction with recovering if other optprobe modifies it.*/ > len = __copy_instruction(buf, p->addr, p->ainsn.insn, &insn); > if (!len) > @@ -467,8 +491,8 @@ static int arch_copy_kprobe(struct kprobe *p) > */ > len = prepare_boost(buf, p, &insn); > > - /* Check whether the instruction modifies Interrupt Flag or not */ > - p->ainsn.if_modifier = is_IF_modifier(buf); > + /* Analyze the opcode and set resume flags */ > + set_resume_flags(p, &insn); > > /* Also, displacement change doesn't affect the first byte */ > p->opcode = buf[0]; > @@ -806,11 +830,6 @@ NOKPROBE_SYMBOL(trampoline_handler); > * 2) If the single-stepped instruction was a call, the return address > * that is atop the stack is the address following the copied instruction. > * We need to make it the address following the original instruction. > - * > - * If this is the first time we've single-stepped the instruction at > - * this probepoint, and the instruction is boostable, boost it: add a > - * jump instruction after the copied instruction, that jumps to the next > - * instruction after the probepoint. > */ > static void resume_execution(struct kprobe *p, struct pt_regs *regs, > struct kprobe_ctlblk *kcb) > @@ -818,59 +837,20 @@ static void resume_execution(struct kprobe *p, struct > pt_regs *regs, > unsigned long *tos = stack_addr(regs); > unsigned long copy_ip = (unsigned long)p->ainsn.insn; > unsigned long orig_ip = (unsigned long)p->addr; > - kprobe_opcode_t *insn = p->ainsn.insn; > - > - /* Skip prefixes */ > - insn = skip_prefixes(insn); > > regs->flags &= ~X86_EFLAGS_TF; > - switch (*insn) { > - case 0x9c: /* pushfl */ > + > + /* Fixup the contents of top of stack */ > + if (p->ainsn.is_pushf) { > *tos &= ~(X86_EFLAGS_TF | X86_EFLAGS_IF); > *tos |= kcb->kprobe_old_flags; > - break; > - case 0xc2: /* iret/ret/lret */ > - case 0xc3: > - case 0xca: > - case 0xcb: > - case 0xcf: > - case 0xea: /* jmp absolute -- ip is correct */ > - /* ip is already adjusted, no more changes required */ > - p->ainsn.boostable = true; > - goto no_change; > - case 0xe8: /* call relative - Fix return addr */ > + } else if (p->ainsn.is_call) { > *tos = orig_ip + (*tos - copy_ip); > - break; > -#ifdef CONFIG_X86_32 > - case 0x9a: /* call absolute -- same as call absolute, indirect */ > - *tos = orig_ip + (*tos - copy_ip); > - goto no_change; > -#endif > - case 0xff: > - if ((insn[1] & 0x30) == 0x10) { > - /* > - * call absolute, indirect > - * Fix return addr; ip is correct. > - * But this is not boostable > - */ > - *tos = orig_ip + (*tos - copy_ip); > - goto no_change; > - } else if (((insn[1] & 0x31) == 0x20) || > - ((insn[1] & 0x31) == 0x21)) { > - /* > - * jmp near and far, absolute indirect > - * ip is correct. And this is boostable > - */ > - p->ainsn.boostable = true; > - goto no_change; > - } > - default: > - break; > } > > - regs->ip += orig_ip - copy_ip; > + if (!p->ainsn.is_abs_ip) > + regs->ip += orig_ip - copy_ip; > > -no_change: > restore_btf(); > } > NOKPROBE_SYMBOL(resume_execution);