| From 87a6b2975f0d340c75b7488d22d61d2f98fb8abf Mon Sep 17 00:00:00 2001 |
| From: Josh Poimboeuf <jpoimboe@redhat.com> |
| Date: Mon, 13 Mar 2017 23:27:47 -0500 |
| Subject: x86/unwind: Fix last frame check for aligned function stacks |
| |
| From: Josh Poimboeuf <jpoimboe@redhat.com> |
| |
| commit 87a6b2975f0d340c75b7488d22d61d2f98fb8abf upstream. |
| |
| Pavel Machek reported the following warning on x86-32: |
| |
| WARNING: kernel stack frame pointer at f50cdf98 in swapper/2:0 has bad value (null) |
| |
| The warning is caused by the unwinder not realizing that it reached the |
| end of the stack, due to an unusual prologue which gcc sometimes |
| generates for aligned stacks. The prologue is based on a gcc feature |
| called the Dynamic Realign Argument Pointer (DRAP). It's almost always |
| enabled for aligned stacks when -maccumulate-outgoing-args isn't set. |
| |
| This issue is similar to the one fixed by the following commit: |
| |
| 8023e0e2a48d ("x86/unwind: Adjust last frame check for aligned function stacks") |
| |
| ... but that fix was specific to x86-64. |
| |
| Make the fix more generic to cover x86-32 as well, and also ensure that |
| the return address referred to by the frame pointer is a copy of the |
| original return address. |
| |
| Fixes: acb4608ad186 ("x86/unwind: Create stack frames for saved syscall registers") |
| Reported-by: Pavel Machek <pavel@ucw.cz> |
| Signed-off-by: Josh Poimboeuf <jpoimboe@redhat.com> |
| Link: http://lkml.kernel.org/r/50d4924db716c264b14f1633037385ec80bf89d2.1489465609.git.jpoimboe@redhat.com |
| Signed-off-by: Thomas Gleixner <tglx@linutronix.de> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| arch/x86/kernel/unwind_frame.c | 36 ++++++++++++++++++++++++++++++------ |
| 1 file changed, 30 insertions(+), 6 deletions(-) |
| |
| --- a/arch/x86/kernel/unwind_frame.c |
| +++ b/arch/x86/kernel/unwind_frame.c |
| @@ -80,19 +80,43 @@ static size_t regs_size(struct pt_regs * |
| return sizeof(*regs); |
| } |
| |
| +#ifdef CONFIG_X86_32 |
| +#define GCC_REALIGN_WORDS 3 |
| +#else |
| +#define GCC_REALIGN_WORDS 1 |
| +#endif |
| + |
| static bool is_last_task_frame(struct unwind_state *state) |
| { |
| - unsigned long bp = (unsigned long)state->bp; |
| - unsigned long regs = (unsigned long)task_pt_regs(state->task); |
| + unsigned long *last_bp = (unsigned long *)task_pt_regs(state->task) - 2; |
| + unsigned long *aligned_bp = last_bp - GCC_REALIGN_WORDS; |
| |
| /* |
| * We have to check for the last task frame at two different locations |
| * because gcc can occasionally decide to realign the stack pointer and |
| - * change the offset of the stack frame by a word in the prologue of a |
| - * function called by head/entry code. |
| + * change the offset of the stack frame in the prologue of a function |
| + * called by head/entry code. Examples: |
| + * |
| + * <start_secondary>: |
| + * push %edi |
| + * lea 0x8(%esp),%edi |
| + * and $0xfffffff8,%esp |
| + * pushl -0x4(%edi) |
| + * push %ebp |
| + * mov %esp,%ebp |
| + * |
| + * <x86_64_start_kernel>: |
| + * lea 0x8(%rsp),%r10 |
| + * and $0xfffffffffffffff0,%rsp |
| + * pushq -0x8(%r10) |
| + * push %rbp |
| + * mov %rsp,%rbp |
| + * |
| + * Note that after aligning the stack, it pushes a duplicate copy of |
| + * the return address before pushing the frame pointer. |
| */ |
| - return bp == regs - FRAME_HEADER_SIZE || |
| - bp == regs - FRAME_HEADER_SIZE - sizeof(long); |
| + return (state->bp == last_bp || |
| + (state->bp == aligned_bp && *(aligned_bp+1) == *(last_bp+1))); |
| } |
| |
| /* |