s390/unwind: fix mixing regs and sp
authorIlya Leoshkevich <iii@linux.ibm.com>
Wed, 2 Oct 2019 11:29:57 +0000 (13:29 +0200)
committerVasily Gorbik <gor@linux.ibm.com>
Thu, 31 Oct 2019 16:26:48 +0000 (17:26 +0100)
unwind_for_each_frame stops after the first frame if regs->gprs[15] <=
sp.

The reason is that in case regs are specified, the first frame should be
regs->psw.addr and the second frame should be sp->gprs[8]. However,
currently the second frame is regs->gprs[15], which confuses
outside_of_stack().

Fix by introducing a flag to distinguish this special case from
unwinding the interrupt handler, for which the current behavior is
appropriate.

Fixes: 78c98f907413 ("s390/unwind: introduce stack unwind API")
Signed-off-by: Ilya Leoshkevich <iii@linux.ibm.com>
Cc: stable@vger.kernel.org # v5.2+
Reviewed-by: Heiko Carstens <heiko.carstens@de.ibm.com>
Signed-off-by: Vasily Gorbik <gor@linux.ibm.com>
arch/s390/include/asm/unwind.h
arch/s390/kernel/unwind_bc.c

index d827b5b..eaaefec 100644 (file)
@@ -35,6 +35,7 @@ struct unwind_state {
        struct task_struct *task;
        struct pt_regs *regs;
        unsigned long sp, ip;
+       bool reuse_sp;
        int graph_idx;
        bool reliable;
        bool error;
index 8fc9daa..a8204f9 100644 (file)
@@ -46,10 +46,15 @@ bool unwind_next_frame(struct unwind_state *state)
 
        regs = state->regs;
        if (unlikely(regs)) {
-               sp = READ_ONCE_NOCHECK(regs->gprs[15]);
-               if (unlikely(outside_of_stack(state, sp))) {
-                       if (!update_stack_info(state, sp))
-                               goto out_err;
+               if (state->reuse_sp) {
+                       sp = state->sp;
+                       state->reuse_sp = false;
+               } else {
+                       sp = READ_ONCE_NOCHECK(regs->gprs[15]);
+                       if (unlikely(outside_of_stack(state, sp))) {
+                               if (!update_stack_info(state, sp))
+                                       goto out_err;
+                       }
                }
                sf = (struct stack_frame *) sp;
                ip = READ_ONCE_NOCHECK(sf->gprs[8]);
@@ -107,9 +112,9 @@ void __unwind_start(struct unwind_state *state, struct task_struct *task,
 {
        struct stack_info *info = &state->stack_info;
        unsigned long *mask = &state->stack_mask;
+       bool reliable, reuse_sp;
        struct stack_frame *sf;
        unsigned long ip;
-       bool reliable;
 
        memset(state, 0, sizeof(*state));
        state->task = task;
@@ -134,10 +139,12 @@ void __unwind_start(struct unwind_state *state, struct task_struct *task,
        if (regs) {
                ip = READ_ONCE_NOCHECK(regs->psw.addr);
                reliable = true;
+               reuse_sp = true;
        } else {
                sf = (struct stack_frame *) sp;
                ip = READ_ONCE_NOCHECK(sf->gprs[8]);
                reliable = false;
+               reuse_sp = false;
        }
 
 #ifdef CONFIG_FUNCTION_GRAPH_TRACER
@@ -151,5 +158,6 @@ void __unwind_start(struct unwind_state *state, struct task_struct *task,
        state->sp = sp;
        state->ip = ip;
        state->reliable = reliable;
+       state->reuse_sp = reuse_sp;
 }
 EXPORT_SYMBOL_GPL(__unwind_start);