xref: /openbmc/linux/arch/s390/kernel/unwind_bc.c (revision 4f2c0a4acffbec01079c28f839422e64ddeff004)
178c98f90SMartin Schwidefsky /* SPDX-License-Identifier: GPL-2.0 */
278c98f90SMartin Schwidefsky #include <linux/sched.h>
378c98f90SMartin Schwidefsky #include <linux/sched/task.h>
478c98f90SMartin Schwidefsky #include <linux/sched/task_stack.h>
578c98f90SMartin Schwidefsky #include <linux/interrupt.h>
678c98f90SMartin Schwidefsky #include <asm/sections.h>
778c98f90SMartin Schwidefsky #include <asm/ptrace.h>
878c98f90SMartin Schwidefsky #include <asm/bitops.h>
978c98f90SMartin Schwidefsky #include <asm/stacktrace.h>
1078c98f90SMartin Schwidefsky #include <asm/unwind.h>
1178c98f90SMartin Schwidefsky 
unwind_get_return_address(struct unwind_state * state)1278c98f90SMartin Schwidefsky unsigned long unwind_get_return_address(struct unwind_state *state)
1378c98f90SMartin Schwidefsky {
1478c98f90SMartin Schwidefsky 	if (unwind_done(state))
1578c98f90SMartin Schwidefsky 		return 0;
1678c98f90SMartin Schwidefsky 	return __kernel_text_address(state->ip) ? state->ip : 0;
1778c98f90SMartin Schwidefsky }
1878c98f90SMartin Schwidefsky EXPORT_SYMBOL_GPL(unwind_get_return_address);
1978c98f90SMartin Schwidefsky 
outside_of_stack(struct unwind_state * state,unsigned long sp)2078c98f90SMartin Schwidefsky static bool outside_of_stack(struct unwind_state *state, unsigned long sp)
2178c98f90SMartin Schwidefsky {
2278c98f90SMartin Schwidefsky 	return (sp <= state->sp) ||
239a159190SVasily Gorbik 		(sp > state->stack_info.end - sizeof(struct stack_frame));
2478c98f90SMartin Schwidefsky }
2578c98f90SMartin Schwidefsky 
update_stack_info(struct unwind_state * state,unsigned long sp)2678c98f90SMartin Schwidefsky static bool update_stack_info(struct unwind_state *state, unsigned long sp)
2778c98f90SMartin Schwidefsky {
2878c98f90SMartin Schwidefsky 	struct stack_info *info = &state->stack_info;
2978c98f90SMartin Schwidefsky 	unsigned long *mask = &state->stack_mask;
3078c98f90SMartin Schwidefsky 
3178c98f90SMartin Schwidefsky 	/* New stack pointer leaves the current stack */
3278c98f90SMartin Schwidefsky 	if (get_stack_info(sp, state->task, info, mask) != 0 ||
3378c98f90SMartin Schwidefsky 	    !on_stack(info, sp, sizeof(struct stack_frame)))
3478c98f90SMartin Schwidefsky 		/* 'sp' does not point to a valid stack */
3578c98f90SMartin Schwidefsky 		return false;
3678c98f90SMartin Schwidefsky 	return true;
3778c98f90SMartin Schwidefsky }
3878c98f90SMartin Schwidefsky 
is_final_pt_regs(struct unwind_state * state,struct pt_regs * regs)39eef06cbfSVasily Gorbik static inline bool is_final_pt_regs(struct unwind_state *state,
40e76e6961SVasily Gorbik 				    struct pt_regs *regs)
41e76e6961SVasily Gorbik {
42eef06cbfSVasily Gorbik 	/* user mode or kernel thread pt_regs at the bottom of task stack */
43eef06cbfSVasily Gorbik 	if (task_pt_regs(state->task) == regs)
44eef06cbfSVasily Gorbik 		return true;
45eef06cbfSVasily Gorbik 
46eef06cbfSVasily Gorbik 	/* user mode pt_regs at the bottom of irq stack */
47eef06cbfSVasily Gorbik 	return state->stack_info.type == STACK_TYPE_IRQ &&
48eef06cbfSVasily Gorbik 	       state->stack_info.end - sizeof(struct pt_regs) == (unsigned long)regs &&
49eef06cbfSVasily Gorbik 	       READ_ONCE_NOCHECK(regs->psw.mask) & PSW_MASK_PSTATE;
50e76e6961SVasily Gorbik }
51e76e6961SVasily Gorbik 
unwind_next_frame(struct unwind_state * state)5278c98f90SMartin Schwidefsky bool unwind_next_frame(struct unwind_state *state)
5378c98f90SMartin Schwidefsky {
5478c98f90SMartin Schwidefsky 	struct stack_info *info = &state->stack_info;
5578c98f90SMartin Schwidefsky 	struct stack_frame *sf;
5678c98f90SMartin Schwidefsky 	struct pt_regs *regs;
5778c98f90SMartin Schwidefsky 	unsigned long sp, ip;
5878c98f90SMartin Schwidefsky 	bool reliable;
5978c98f90SMartin Schwidefsky 
6078c98f90SMartin Schwidefsky 	regs = state->regs;
6178c98f90SMartin Schwidefsky 	if (unlikely(regs)) {
62a1d863acSIlya Leoshkevich 		sp = state->sp;
6378c98f90SMartin Schwidefsky 		sf = (struct stack_frame *) sp;
6420955746SVasily Gorbik 		ip = READ_ONCE_NOCHECK(sf->gprs[8]);
6578c98f90SMartin Schwidefsky 		reliable = false;
6678c98f90SMartin Schwidefsky 		regs = NULL;
67*708b1376SVasily Gorbik 		/* skip bogus %r14 or if is the same as regs->psw.addr */
68*708b1376SVasily Gorbik 		if (!__kernel_text_address(ip) || state->ip == unwind_recover_ret_addr(state, ip)) {
69bf018ee6SVasily Gorbik 			state->regs = NULL;
70bf018ee6SVasily Gorbik 			return unwind_next_frame(state);
71bf018ee6SVasily Gorbik 		}
7278c98f90SMartin Schwidefsky 	} else {
7378c98f90SMartin Schwidefsky 		sf = (struct stack_frame *) state->sp;
7420955746SVasily Gorbik 		sp = READ_ONCE_NOCHECK(sf->back_chain);
7578c98f90SMartin Schwidefsky 		if (likely(sp)) {
7678c98f90SMartin Schwidefsky 			/* Non-zero back-chain points to the previous frame */
7778c98f90SMartin Schwidefsky 			if (unlikely(outside_of_stack(state, sp))) {
7878c98f90SMartin Schwidefsky 				if (!update_stack_info(state, sp))
7978c98f90SMartin Schwidefsky 					goto out_err;
8078c98f90SMartin Schwidefsky 			}
8178c98f90SMartin Schwidefsky 			sf = (struct stack_frame *) sp;
8220955746SVasily Gorbik 			ip = READ_ONCE_NOCHECK(sf->gprs[8]);
8378c98f90SMartin Schwidefsky 			reliable = true;
8478c98f90SMartin Schwidefsky 		} else {
8578c98f90SMartin Schwidefsky 			/* No back-chain, look for a pt_regs structure */
8678c98f90SMartin Schwidefsky 			sp = state->sp + STACK_FRAME_OVERHEAD;
8778c98f90SMartin Schwidefsky 			if (!on_stack(info, sp, sizeof(struct pt_regs)))
8867f55934SVasily Gorbik 				goto out_err;
8978c98f90SMartin Schwidefsky 			regs = (struct pt_regs *) sp;
90eef06cbfSVasily Gorbik 			if (is_final_pt_regs(state, regs))
9178c98f90SMartin Schwidefsky 				goto out_stop;
9220955746SVasily Gorbik 			ip = READ_ONCE_NOCHECK(regs->psw.addr);
9397806dfbSVasily Gorbik 			sp = READ_ONCE_NOCHECK(regs->gprs[15]);
9497806dfbSVasily Gorbik 			if (unlikely(outside_of_stack(state, sp))) {
9597806dfbSVasily Gorbik 				if (!update_stack_info(state, sp))
9697806dfbSVasily Gorbik 					goto out_err;
9797806dfbSVasily Gorbik 			}
9878c98f90SMartin Schwidefsky 			reliable = true;
9978c98f90SMartin Schwidefsky 		}
10078c98f90SMartin Schwidefsky 	}
10178c98f90SMartin Schwidefsky 
102be2d11b2SMiroslav Benes 	/* Sanity check: ABI requires SP to be aligned 8 bytes. */
103be2d11b2SMiroslav Benes 	if (sp & 0x7)
104be2d11b2SMiroslav Benes 		goto out_err;
105be2d11b2SMiroslav Benes 
10678c98f90SMartin Schwidefsky 	/* Update unwind state */
10778c98f90SMartin Schwidefsky 	state->sp = sp;
10878c98f90SMartin Schwidefsky 	state->regs = regs;
10978c98f90SMartin Schwidefsky 	state->reliable = reliable;
110d81675b6SVasily Gorbik 	state->ip = unwind_recover_ret_addr(state, ip);
11178c98f90SMartin Schwidefsky 	return true;
11278c98f90SMartin Schwidefsky 
11378c98f90SMartin Schwidefsky out_err:
11478c98f90SMartin Schwidefsky 	state->error = true;
11578c98f90SMartin Schwidefsky out_stop:
11678c98f90SMartin Schwidefsky 	state->stack_info.type = STACK_TYPE_UNKNOWN;
11778c98f90SMartin Schwidefsky 	return false;
11878c98f90SMartin Schwidefsky }
11978c98f90SMartin Schwidefsky EXPORT_SYMBOL_GPL(unwind_next_frame);
12078c98f90SMartin Schwidefsky 
__unwind_start(struct unwind_state * state,struct task_struct * task,struct pt_regs * regs,unsigned long first_frame)12178c98f90SMartin Schwidefsky void __unwind_start(struct unwind_state *state, struct task_struct *task,
122222ee908SVasily Gorbik 		    struct pt_regs *regs, unsigned long first_frame)
12378c98f90SMartin Schwidefsky {
12478c98f90SMartin Schwidefsky 	struct stack_info *info = &state->stack_info;
12578c98f90SMartin Schwidefsky 	struct stack_frame *sf;
126222ee908SVasily Gorbik 	unsigned long ip, sp;
12778c98f90SMartin Schwidefsky 
12878c98f90SMartin Schwidefsky 	memset(state, 0, sizeof(*state));
12978c98f90SMartin Schwidefsky 	state->task = task;
13078c98f90SMartin Schwidefsky 	state->regs = regs;
13178c98f90SMartin Schwidefsky 
13278c98f90SMartin Schwidefsky 	/* Don't even attempt to start from user mode regs: */
13378c98f90SMartin Schwidefsky 	if (regs && user_mode(regs)) {
13478c98f90SMartin Schwidefsky 		info->type = STACK_TYPE_UNKNOWN;
13578c98f90SMartin Schwidefsky 		return;
13678c98f90SMartin Schwidefsky 	}
13778c98f90SMartin Schwidefsky 
138222ee908SVasily Gorbik 	/* Get the instruction pointer from pt_regs or the stack frame */
139222ee908SVasily Gorbik 	if (regs) {
140222ee908SVasily Gorbik 		ip = regs->psw.addr;
141222ee908SVasily Gorbik 		sp = regs->gprs[15];
142222ee908SVasily Gorbik 	} else if (task == current) {
143222ee908SVasily Gorbik 		sp = current_frame_address();
144222ee908SVasily Gorbik 	} else {
145222ee908SVasily Gorbik 		sp = task->thread.ksp;
146222ee908SVasily Gorbik 	}
147222ee908SVasily Gorbik 
14878c98f90SMartin Schwidefsky 	/* Get current stack pointer and initialize stack info */
149222ee908SVasily Gorbik 	if (!update_stack_info(state, sp)) {
15078c98f90SMartin Schwidefsky 		/* Something is wrong with the stack pointer */
15178c98f90SMartin Schwidefsky 		info->type = STACK_TYPE_UNKNOWN;
15278c98f90SMartin Schwidefsky 		state->error = true;
15378c98f90SMartin Schwidefsky 		return;
15478c98f90SMartin Schwidefsky 	}
15578c98f90SMartin Schwidefsky 
156222ee908SVasily Gorbik 	if (!regs) {
157222ee908SVasily Gorbik 		/* Stack frame is within valid stack */
15878c98f90SMartin Schwidefsky 		sf = (struct stack_frame *)sp;
15920955746SVasily Gorbik 		ip = READ_ONCE_NOCHECK(sf->gprs[8]);
16078c98f90SMartin Schwidefsky 	}
16178c98f90SMartin Schwidefsky 
16278c98f90SMartin Schwidefsky 	/* Update unwind state */
16378c98f90SMartin Schwidefsky 	state->sp = sp;
164222ee908SVasily Gorbik 	state->reliable = true;
165d81675b6SVasily Gorbik 	state->ip = unwind_recover_ret_addr(state, ip);
166222ee908SVasily Gorbik 
167222ee908SVasily Gorbik 	if (!first_frame)
168222ee908SVasily Gorbik 		return;
169222ee908SVasily Gorbik 	/* Skip through the call chain to the specified starting frame */
170222ee908SVasily Gorbik 	while (!unwind_done(state)) {
171222ee908SVasily Gorbik 		if (on_stack(&state->stack_info, first_frame, sizeof(struct stack_frame))) {
172222ee908SVasily Gorbik 			if (state->sp >= first_frame)
173222ee908SVasily Gorbik 				break;
174222ee908SVasily Gorbik 		}
175222ee908SVasily Gorbik 		unwind_next_frame(state);
176222ee908SVasily Gorbik 	}
17778c98f90SMartin Schwidefsky }
17878c98f90SMartin Schwidefsky EXPORT_SYMBOL_GPL(__unwind_start);
179