diff options
Diffstat (limited to 'arch/x86/kernel/ptrace.c')
-rw-r--r-- | arch/x86/kernel/ptrace.c | 37 |
1 files changed, 37 insertions, 0 deletions
diff --git a/arch/x86/kernel/ptrace.c b/arch/x86/kernel/ptrace.c index b00b33a18390..974b67e46dd0 100644 --- a/arch/x86/kernel/ptrace.c +++ b/arch/x86/kernel/ptrace.c | |||
@@ -22,6 +22,7 @@ | |||
22 | #include <linux/perf_event.h> | 22 | #include <linux/perf_event.h> |
23 | #include <linux/hw_breakpoint.h> | 23 | #include <linux/hw_breakpoint.h> |
24 | #include <linux/rcupdate.h> | 24 | #include <linux/rcupdate.h> |
25 | #include <linux/module.h> | ||
25 | 26 | ||
26 | #include <asm/uaccess.h> | 27 | #include <asm/uaccess.h> |
27 | #include <asm/pgtable.h> | 28 | #include <asm/pgtable.h> |
@@ -166,6 +167,35 @@ static inline bool invalid_selector(u16 value) | |||
166 | 167 | ||
167 | #define FLAG_MASK FLAG_MASK_32 | 168 | #define FLAG_MASK FLAG_MASK_32 |
168 | 169 | ||
170 | /* | ||
171 | * X86_32 CPUs don't save ss and esp if the CPU is already in kernel mode | ||
172 | * when it traps. The previous stack will be directly underneath the saved | ||
173 | * registers, and 'sp/ss' won't even have been saved. Thus the '®s->sp'. | ||
174 | * | ||
175 | * Now, if the stack is empty, '®s->sp' is out of range. In this | ||
176 | * case we try to take the previous stack. To always return a non-null | ||
177 | * stack pointer we fall back to regs as stack if no previous stack | ||
178 | * exists. | ||
179 | * | ||
180 | * This is valid only for kernel mode traps. | ||
181 | */ | ||
182 | unsigned long kernel_stack_pointer(struct pt_regs *regs) | ||
183 | { | ||
184 | unsigned long context = (unsigned long)regs & ~(THREAD_SIZE - 1); | ||
185 | unsigned long sp = (unsigned long)®s->sp; | ||
186 | struct thread_info *tinfo; | ||
187 | |||
188 | if (context == (sp & ~(THREAD_SIZE - 1))) | ||
189 | return sp; | ||
190 | |||
191 | tinfo = (struct thread_info *)context; | ||
192 | if (tinfo->previous_esp) | ||
193 | return tinfo->previous_esp; | ||
194 | |||
195 | return (unsigned long)regs; | ||
196 | } | ||
197 | EXPORT_SYMBOL_GPL(kernel_stack_pointer); | ||
198 | |||
169 | static unsigned long *pt_regs_access(struct pt_regs *regs, unsigned long regno) | 199 | static unsigned long *pt_regs_access(struct pt_regs *regs, unsigned long regno) |
170 | { | 200 | { |
171 | BUILD_BUG_ON(offsetof(struct pt_regs, bx) != 0); | 201 | BUILD_BUG_ON(offsetof(struct pt_regs, bx) != 0); |
@@ -1511,6 +1541,13 @@ void syscall_trace_leave(struct pt_regs *regs) | |||
1511 | { | 1541 | { |
1512 | bool step; | 1542 | bool step; |
1513 | 1543 | ||
1544 | /* | ||
1545 | * We may come here right after calling schedule_user() | ||
1546 | * or do_notify_resume(), in which case we can be in RCU | ||
1547 | * user mode. | ||
1548 | */ | ||
1549 | rcu_user_exit(); | ||
1550 | |||
1514 | audit_syscall_exit(regs); | 1551 | audit_syscall_exit(regs); |
1515 | 1552 | ||
1516 | if (unlikely(test_thread_flag(TIF_SYSCALL_TRACEPOINT))) | 1553 | if (unlikely(test_thread_flag(TIF_SYSCALL_TRACEPOINT))) |