uprobes/x86: Fix the wrong ->si_addr when xol triggers a trap
If the probed insn triggers a trap, ->si_addr = regs->ip is technically correct, but this is not what the signal handler wants; we need to pass the address of the probed insn, not the address of xol slot. Add the new arch-agnostic helper, uprobe_get_trap_addr(), and change fill_trap_info() and math_error() to use it. !CONFIG_UPROBES case in uprobes.h uses a macro to avoid include hell and ensure that it can be compiled even if an architecture doesn't define instruction_pointer(). Test-case: #include <signal.h> #include <stdio.h> #include <unistd.h> extern void probe_div(void); void sigh(int sig, siginfo_t *info, void *c) { int passed = (info->si_addr == probe_div); printf(passed ? "PASS\n" : "FAIL\n"); _exit(!passed); } int main(void) { struct sigaction sa = { .sa_sigaction = sigh, .sa_flags = SA_SIGINFO, }; sigaction(SIGFPE, &sa, NULL); asm ( "xor %ecx,%ecx\n" ".globl probe_div; probe_div:\n" "idiv %ecx\n" ); return 0; } it fails if probe_div() is probed. Note: show_unhandled_signals users should probably use this helper too, but we need to cleanup them first. Signed-off-by: Oleg Nesterov <oleg@redhat.com> Reviewed-by: Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com>
This commit is contained in:
Родитель
0eb14833d5
Коммит
b02ef20a9f
|
@ -23,6 +23,7 @@
|
||||||
#include <linux/kernel.h>
|
#include <linux/kernel.h>
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/ptrace.h>
|
#include <linux/ptrace.h>
|
||||||
|
#include <linux/uprobes.h>
|
||||||
#include <linux/string.h>
|
#include <linux/string.h>
|
||||||
#include <linux/delay.h>
|
#include <linux/delay.h>
|
||||||
#include <linux/errno.h>
|
#include <linux/errno.h>
|
||||||
|
@ -148,11 +149,11 @@ static siginfo_t *fill_trap_info(struct pt_regs *regs, int signr, int trapnr,
|
||||||
|
|
||||||
case X86_TRAP_DE:
|
case X86_TRAP_DE:
|
||||||
sicode = FPE_INTDIV;
|
sicode = FPE_INTDIV;
|
||||||
siaddr = regs->ip;
|
siaddr = uprobe_get_trap_addr(regs);
|
||||||
break;
|
break;
|
||||||
case X86_TRAP_UD:
|
case X86_TRAP_UD:
|
||||||
sicode = ILL_ILLOPN;
|
sicode = ILL_ILLOPN;
|
||||||
siaddr = regs->ip;
|
siaddr = uprobe_get_trap_addr(regs);
|
||||||
break;
|
break;
|
||||||
case X86_TRAP_AC:
|
case X86_TRAP_AC:
|
||||||
sicode = BUS_ADRALN;
|
sicode = BUS_ADRALN;
|
||||||
|
@ -531,7 +532,7 @@ static void math_error(struct pt_regs *regs, int error_code, int trapnr)
|
||||||
task->thread.error_code = error_code;
|
task->thread.error_code = error_code;
|
||||||
info.si_signo = SIGFPE;
|
info.si_signo = SIGFPE;
|
||||||
info.si_errno = 0;
|
info.si_errno = 0;
|
||||||
info.si_addr = (void __user *)regs->ip;
|
info.si_addr = (void __user *)uprobe_get_trap_addr(regs);
|
||||||
if (trapnr == X86_TRAP_MF) {
|
if (trapnr == X86_TRAP_MF) {
|
||||||
unsigned short cwd, swd;
|
unsigned short cwd, swd;
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -102,6 +102,7 @@ extern int __weak set_orig_insn(struct arch_uprobe *aup, struct mm_struct *mm, u
|
||||||
extern bool __weak is_swbp_insn(uprobe_opcode_t *insn);
|
extern bool __weak is_swbp_insn(uprobe_opcode_t *insn);
|
||||||
extern bool __weak is_trap_insn(uprobe_opcode_t *insn);
|
extern bool __weak is_trap_insn(uprobe_opcode_t *insn);
|
||||||
extern unsigned long __weak uprobe_get_swbp_addr(struct pt_regs *regs);
|
extern unsigned long __weak uprobe_get_swbp_addr(struct pt_regs *regs);
|
||||||
|
extern unsigned long uprobe_get_trap_addr(struct pt_regs *regs);
|
||||||
extern int uprobe_write_opcode(struct mm_struct *mm, unsigned long vaddr, uprobe_opcode_t);
|
extern int uprobe_write_opcode(struct mm_struct *mm, unsigned long vaddr, uprobe_opcode_t);
|
||||||
extern int uprobe_register(struct inode *inode, loff_t offset, struct uprobe_consumer *uc);
|
extern int uprobe_register(struct inode *inode, loff_t offset, struct uprobe_consumer *uc);
|
||||||
extern int uprobe_apply(struct inode *inode, loff_t offset, struct uprobe_consumer *uc, bool);
|
extern int uprobe_apply(struct inode *inode, loff_t offset, struct uprobe_consumer *uc, bool);
|
||||||
|
@ -130,6 +131,9 @@ extern bool __weak arch_uprobe_ignore(struct arch_uprobe *aup, struct pt_regs *r
|
||||||
#else /* !CONFIG_UPROBES */
|
#else /* !CONFIG_UPROBES */
|
||||||
struct uprobes_state {
|
struct uprobes_state {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define uprobe_get_trap_addr(regs) instruction_pointer(regs)
|
||||||
|
|
||||||
static inline int
|
static inline int
|
||||||
uprobe_register(struct inode *inode, loff_t offset, struct uprobe_consumer *uc)
|
uprobe_register(struct inode *inode, loff_t offset, struct uprobe_consumer *uc)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1351,6 +1351,16 @@ unsigned long __weak uprobe_get_swbp_addr(struct pt_regs *regs)
|
||||||
return instruction_pointer(regs) - UPROBE_SWBP_INSN_SIZE;
|
return instruction_pointer(regs) - UPROBE_SWBP_INSN_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsigned long uprobe_get_trap_addr(struct pt_regs *regs)
|
||||||
|
{
|
||||||
|
struct uprobe_task *utask = current->utask;
|
||||||
|
|
||||||
|
if (unlikely(utask && utask->active_uprobe))
|
||||||
|
return utask->vaddr;
|
||||||
|
|
||||||
|
return instruction_pointer(regs);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called with no locks held.
|
* Called with no locks held.
|
||||||
* Called in context of a exiting or a exec-ing thread.
|
* Called in context of a exiting or a exec-ing thread.
|
||||||
|
|
Загрузка…
Ссылка в новой задаче