[PATCH] x86_64: Regularize exception stack handling

This fixes various issues in the return path for "paranoid"
handlers (= running on a private exception stack that act like NMIs).

Generalize previous hack to switch back to process stack for
scheduling/signal handling purposes.

Signed-off-by: Andi Kleen <ak@suse.de>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
This commit is contained in:
Andi Kleen 2005-04-16 15:25:03 -07:00 committed by Linus Torvalds
parent 11b854b2f1
commit 6fefb0d175
2 changed files with 56 additions and 52 deletions

View File

@ -579,6 +579,7 @@ ENTRY(spurious_interrupt)
movq ORIG_RAX(%rsp),%rsi movq ORIG_RAX(%rsp),%rsi
movq $-1,ORIG_RAX(%rsp) movq $-1,ORIG_RAX(%rsp)
call \sym call \sym
cli
.endm .endm
/* /*
@ -794,10 +795,6 @@ ENTRY(debug)
pushq $0 pushq $0
CFI_ADJUST_CFA_OFFSET 8 CFI_ADJUST_CFA_OFFSET 8
paranoidentry do_debug paranoidentry do_debug
/* switch back to process stack to restore the state ptrace touched */
movq %rax,%rsp
testl $3,CS(%rsp)
jnz paranoid_userspace
jmp paranoid_exit jmp paranoid_exit
CFI_ENDPROC CFI_ENDPROC
@ -807,33 +804,47 @@ ENTRY(nmi)
pushq $-1 pushq $-1
CFI_ADJUST_CFA_OFFSET 8 CFI_ADJUST_CFA_OFFSET 8
paranoidentry do_nmi paranoidentry do_nmi
/*
* "Paranoid" exit path from exception stack.
* Paranoid because this is used by NMIs and cannot take
* any kernel state for granted.
* We don't do kernel preemption checks here, because only
* NMI should be common and it does not enable IRQs and
* cannot get reschedule ticks.
*/
/* ebx: no swapgs flag */ /* ebx: no swapgs flag */
paranoid_exit: paranoid_exit:
testl %ebx,%ebx /* swapgs needed? */ testl %ebx,%ebx /* swapgs needed? */
jnz paranoid_restore jnz paranoid_restore
testl $3,CS(%rsp)
jnz paranoid_userspace
paranoid_swapgs: paranoid_swapgs:
cli
swapgs swapgs
paranoid_restore: paranoid_restore:
RESTORE_ALL 8 RESTORE_ALL 8
iretq iretq
paranoid_userspace: paranoid_userspace:
cli
GET_THREAD_INFO(%rcx) GET_THREAD_INFO(%rcx)
movl threadinfo_flags(%rcx),%edx movl threadinfo_flags(%rcx),%ebx
testl $_TIF_WORK_MASK,%edx andl $_TIF_WORK_MASK,%ebx
jz paranoid_swapgs jz paranoid_swapgs
testl $_TIF_NEED_RESCHED,%edx
jnz paranoid_resched
sti
xorl %esi,%esi /* oldset */
movq %rsp,%rdi /* &pt_regs */ movq %rsp,%rdi /* &pt_regs */
call sync_regs
movq %rax,%rsp /* switch stack for scheduling */
testl $_TIF_NEED_RESCHED,%ebx
jnz paranoid_schedule
movl %ebx,%edx /* arg3: thread flags */
sti
xorl %esi,%esi /* arg2: oldset */
movq %rsp,%rdi /* arg1: &pt_regs */
call do_notify_resume call do_notify_resume
jmp paranoid_exit cli
paranoid_resched: jmp paranoid_userspace
paranoid_schedule:
sti sti
call schedule call schedule
jmp paranoid_exit cli
jmp paranoid_userspace
CFI_ENDPROC CFI_ENDPROC
ENTRY(int3) ENTRY(int3)
@ -858,9 +869,6 @@ ENTRY(reserved)
ENTRY(double_fault) ENTRY(double_fault)
CFI_STARTPROC CFI_STARTPROC
paranoidentry do_double_fault paranoidentry do_double_fault
movq %rax,%rsp
testl $3,CS(%rsp)
jnz paranoid_userspace
jmp paranoid_exit jmp paranoid_exit
CFI_ENDPROC CFI_ENDPROC
@ -874,9 +882,6 @@ ENTRY(segment_not_present)
ENTRY(stack_segment) ENTRY(stack_segment)
CFI_STARTPROC CFI_STARTPROC
paranoidentry do_stack_segment paranoidentry do_stack_segment
movq %rax,%rsp
testl $3,CS(%rsp)
jnz paranoid_userspace
jmp paranoid_exit jmp paranoid_exit
CFI_ENDPROC CFI_ENDPROC

View File

@ -488,24 +488,8 @@ DO_ERROR(10, SIGSEGV, "invalid TSS", invalid_TSS)
DO_ERROR(11, SIGBUS, "segment not present", segment_not_present) DO_ERROR(11, SIGBUS, "segment not present", segment_not_present)
DO_ERROR_INFO(17, SIGBUS, "alignment check", alignment_check, BUS_ADRALN, 0) DO_ERROR_INFO(17, SIGBUS, "alignment check", alignment_check, BUS_ADRALN, 0)
DO_ERROR(18, SIGSEGV, "reserved", reserved) DO_ERROR(18, SIGSEGV, "reserved", reserved)
DO_ERROR(12, SIGBUS, "stack segment", stack_segment)
#define DO_ERROR_STACK(trapnr, signr, str, name) \ DO_ERROR( 8, SIGSEGV, "double fault", double_fault)
asmlinkage void *do_##name(struct pt_regs * regs, long error_code) \
{ \
struct pt_regs *pr = ((struct pt_regs *)(current->thread.rsp0))-1; \
if (notify_die(DIE_TRAP, str, regs, error_code, trapnr, signr) \
== NOTIFY_STOP) \
return regs; \
if (regs->cs & 3) { \
memcpy(pr, regs, sizeof(struct pt_regs)); \
regs = pr; \
} \
do_trap(trapnr, signr, str, regs, error_code, NULL); \
return regs; \
}
DO_ERROR_STACK(12, SIGBUS, "stack segment", stack_segment)
DO_ERROR_STACK( 8, SIGSEGV, "double fault", double_fault)
asmlinkage void do_general_protection(struct pt_regs * regs, long error_code) asmlinkage void do_general_protection(struct pt_regs * regs, long error_code)
{ {
@ -584,6 +568,8 @@ static void unknown_nmi_error(unsigned char reason, struct pt_regs * regs)
printk("Do you have a strange power saving mode enabled?\n"); printk("Do you have a strange power saving mode enabled?\n");
} }
/* Runs on IST stack. This code must keep interrupts off all the time.
Nested NMIs are prevented by the CPU. */
asmlinkage void default_do_nmi(struct pt_regs *regs) asmlinkage void default_do_nmi(struct pt_regs *regs)
{ {
unsigned char reason = 0; unsigned char reason = 0;
@ -629,20 +615,34 @@ asmlinkage void do_int3(struct pt_regs * regs, long error_code)
return; return;
} }
/* runs on IST stack. */ /* Help handler running on IST stack to switch back to user stack
asmlinkage void *do_debug(struct pt_regs * regs, unsigned long error_code) for scheduling or signal handling. The actual stack switch is done in
entry.S */
asmlinkage struct pt_regs *sync_regs(struct pt_regs *eregs)
{
struct pt_regs *regs = eregs;
/* Did already sync */
if (eregs == (struct pt_regs *)eregs->rsp)
;
/* Exception from user space */
else if (eregs->cs & 3)
regs = ((struct pt_regs *)current->thread.rsp0) - 1;
/* Exception from kernel and interrupts are enabled. Move to
kernel process stack. */
else if (eregs->eflags & X86_EFLAGS_IF)
regs = (struct pt_regs *)(eregs->rsp -= sizeof(struct pt_regs));
if (eregs != regs)
*regs = *eregs;
return regs;
}
/* runs on IST stack. */
asmlinkage void do_debug(struct pt_regs * regs, unsigned long error_code)
{ {
struct pt_regs *pr;
unsigned long condition; unsigned long condition;
struct task_struct *tsk = current; struct task_struct *tsk = current;
siginfo_t info; siginfo_t info;
pr = (struct pt_regs *)(current->thread.rsp0)-1;
if (regs->cs & 3) {
memcpy(pr, regs, sizeof(struct pt_regs));
regs = pr;
}
#ifdef CONFIG_CHECKING #ifdef CONFIG_CHECKING
{ {
/* RED-PEN interaction with debugger - could destroy gs */ /* RED-PEN interaction with debugger - could destroy gs */
@ -660,7 +660,7 @@ asmlinkage void *do_debug(struct pt_regs * regs, unsigned long error_code)
if (notify_die(DIE_DEBUG, "debug", regs, condition, error_code, if (notify_die(DIE_DEBUG, "debug", regs, condition, error_code,
SIGTRAP) == NOTIFY_STOP) { SIGTRAP) == NOTIFY_STOP) {
return regs; return;
} }
conditional_sti(regs); conditional_sti(regs);
@ -712,7 +712,7 @@ asmlinkage void *do_debug(struct pt_regs * regs, unsigned long error_code)
clear_dr7: clear_dr7:
asm volatile("movq %0,%%db7"::"r"(0UL)); asm volatile("movq %0,%%db7"::"r"(0UL));
notify_die(DIE_DEBUG, "debug", regs, condition, 1, SIGTRAP); notify_die(DIE_DEBUG, "debug", regs, condition, 1, SIGTRAP);
return regs; return;
clear_TF_reenable: clear_TF_reenable:
set_tsk_thread_flag(tsk, TIF_SINGLESTEP); set_tsk_thread_flag(tsk, TIF_SINGLESTEP);
@ -722,7 +722,6 @@ asmlinkage void *do_debug(struct pt_regs * regs, unsigned long error_code)
if (notify_die(DIE_DEBUG, "debug2", regs, condition, 1, SIGTRAP) if (notify_die(DIE_DEBUG, "debug2", regs, condition, 1, SIGTRAP)
!= NOTIFY_STOP) != NOTIFY_STOP)
regs->eflags &= ~TF_MASK; regs->eflags &= ~TF_MASK;
return regs;
} }
static int kernel_math_error(struct pt_regs *regs, char *str) static int kernel_math_error(struct pt_regs *regs, char *str)