2019-01-28 08:33:08 +01:00
/* SPDX-License-Identifier: GPL-2.0 */
# include <linux/sched.h>
# include <linux/sched/task.h>
# include <linux/sched/task_stack.h>
# include <linux/interrupt.h>
# include <asm/sections.h>
# include <asm/ptrace.h>
# include <asm/bitops.h>
# include <asm/stacktrace.h>
# include <asm/unwind.h>
unsigned long unwind_get_return_address ( struct unwind_state * state )
{
if ( unwind_done ( state ) )
return 0 ;
return __kernel_text_address ( state - > ip ) ? state - > ip : 0 ;
}
EXPORT_SYMBOL_GPL ( unwind_get_return_address ) ;
static bool outside_of_stack ( struct unwind_state * state , unsigned long sp )
{
return ( sp < = state - > sp ) | |
2019-07-08 14:24:38 +02:00
( sp > state - > stack_info . end - sizeof ( struct stack_frame ) ) ;
2019-01-28 08:33:08 +01:00
}
static bool update_stack_info ( struct unwind_state * state , unsigned long sp )
{
struct stack_info * info = & state - > stack_info ;
unsigned long * mask = & state - > stack_mask ;
/* New stack pointer leaves the current stack */
if ( get_stack_info ( sp , state - > task , info , mask ) ! = 0 | |
! on_stack ( info , sp , sizeof ( struct stack_frame ) ) )
/* 'sp' does not point to a valid stack */
return false ;
return true ;
}
2019-12-11 17:27:31 +01:00
static inline bool is_final_pt_regs ( struct unwind_state * state ,
struct pt_regs * regs )
2019-11-22 16:49:13 +01:00
{
2019-12-11 17:27:31 +01:00
/* user mode or kernel thread pt_regs at the bottom of task stack */
if ( task_pt_regs ( state - > task ) = = regs )
return true ;
/* user mode pt_regs at the bottom of irq stack */
return state - > stack_info . type = = STACK_TYPE_IRQ & &
state - > stack_info . end - sizeof ( struct pt_regs ) = = ( unsigned long ) regs & &
READ_ONCE_NOCHECK ( regs - > psw . mask ) & PSW_MASK_PSTATE ;
2019-11-22 16:49:13 +01:00
}
2019-01-28 08:33:08 +01:00
bool unwind_next_frame ( struct unwind_state * state )
{
struct stack_info * info = & state - > stack_info ;
struct stack_frame * sf ;
struct pt_regs * regs ;
unsigned long sp , ip ;
bool reliable ;
regs = state - > regs ;
if ( unlikely ( regs ) ) {
2019-11-22 15:58:42 +01:00
sp = state - > sp ;
2019-01-28 08:33:08 +01:00
sf = ( struct stack_frame * ) sp ;
2019-06-20 10:18:31 +02:00
ip = READ_ONCE_NOCHECK ( sf - > gprs [ 8 ] ) ;
2019-01-28 08:33:08 +01:00
reliable = false ;
regs = NULL ;
2019-11-27 18:12:04 +01:00
if ( ! __kernel_text_address ( ip ) ) {
/* skip bogus %r14 */
state - > regs = NULL ;
return unwind_next_frame ( state ) ;
}
2019-01-28 08:33:08 +01:00
} else {
sf = ( struct stack_frame * ) state - > sp ;
2019-06-20 10:18:31 +02:00
sp = READ_ONCE_NOCHECK ( sf - > back_chain ) ;
2019-01-28 08:33:08 +01:00
if ( likely ( sp ) ) {
/* Non-zero back-chain points to the previous frame */
if ( unlikely ( outside_of_stack ( state , sp ) ) ) {
if ( ! update_stack_info ( state , sp ) )
goto out_err ;
}
sf = ( struct stack_frame * ) sp ;
2019-06-20 10:18:31 +02:00
ip = READ_ONCE_NOCHECK ( sf - > gprs [ 8 ] ) ;
2019-01-28 08:33:08 +01:00
reliable = true ;
} else {
/* No back-chain, look for a pt_regs structure */
sp = state - > sp + STACK_FRAME_OVERHEAD ;
if ( ! on_stack ( info , sp , sizeof ( struct pt_regs ) ) )
2019-11-22 15:53:30 +01:00
goto out_err ;
2019-01-28 08:33:08 +01:00
regs = ( struct pt_regs * ) sp ;
2019-12-11 17:27:31 +01:00
if ( is_final_pt_regs ( state , regs ) )
2019-01-28 08:33:08 +01:00
goto out_stop ;
2019-06-20 10:18:31 +02:00
ip = READ_ONCE_NOCHECK ( regs - > psw . addr ) ;
2019-11-22 15:58:42 +01:00
sp = READ_ONCE_NOCHECK ( regs - > gprs [ 15 ] ) ;
if ( unlikely ( outside_of_stack ( state , sp ) ) ) {
if ( ! update_stack_info ( state , sp ) )
goto out_err ;
}
2019-01-28 08:33:08 +01:00
reliable = true ;
}
}
2019-11-27 19:35:19 +01:00
/* Sanity check: ABI requires SP to be aligned 8 bytes. */
if ( sp & 0x7 )
goto out_err ;
2019-10-29 15:39:02 +01:00
ip = ftrace_graph_ret_addr ( state - > task , & state - > graph_idx , ip , ( void * ) sp ) ;
2019-01-28 08:33:08 +01:00
/* Update unwind state */
state - > sp = sp ;
state - > ip = ip ;
state - > regs = regs ;
state - > reliable = reliable ;
return true ;
out_err :
state - > error = true ;
out_stop :
state - > stack_info . type = STACK_TYPE_UNKNOWN ;
return false ;
}
EXPORT_SYMBOL_GPL ( unwind_next_frame ) ;
void __unwind_start ( struct unwind_state * state , struct task_struct * task ,
2019-11-27 17:37:51 +01:00
struct pt_regs * regs , unsigned long first_frame )
2019-01-28 08:33:08 +01:00
{
struct stack_info * info = & state - > stack_info ;
struct stack_frame * sf ;
2019-11-27 17:37:51 +01:00
unsigned long ip , sp ;
2019-01-28 08:33:08 +01:00
memset ( state , 0 , sizeof ( * state ) ) ;
state - > task = task ;
state - > regs = regs ;
/* Don't even attempt to start from user mode regs: */
if ( regs & & user_mode ( regs ) ) {
info - > type = STACK_TYPE_UNKNOWN ;
return ;
}
2019-11-27 17:37:51 +01:00
/* Get the instruction pointer from pt_regs or the stack frame */
if ( regs ) {
ip = regs - > psw . addr ;
sp = regs - > gprs [ 15 ] ;
} else if ( task = = current ) {
sp = current_frame_address ( ) ;
} else {
sp = task - > thread . ksp ;
}
2019-01-28 08:33:08 +01:00
/* Get current stack pointer and initialize stack info */
2019-11-27 17:37:51 +01:00
if ( ! update_stack_info ( state , sp ) ) {
2019-01-28 08:33:08 +01:00
/* Something is wrong with the stack pointer */
info - > type = STACK_TYPE_UNKNOWN ;
state - > error = true ;
return ;
}
2019-11-27 17:37:51 +01:00
if ( ! regs ) {
/* Stack frame is within valid stack */
sf = ( struct stack_frame * ) sp ;
2019-06-20 10:18:31 +02:00
ip = READ_ONCE_NOCHECK ( sf - > gprs [ 8 ] ) ;
2019-01-28 08:33:08 +01:00
}
2019-10-29 15:39:02 +01:00
ip = ftrace_graph_ret_addr ( state - > task , & state - > graph_idx , ip , NULL ) ;
2019-01-28 08:33:08 +01:00
/* Update unwind state */
state - > sp = sp ;
state - > ip = ip ;
2019-11-27 17:37:51 +01:00
state - > reliable = true ;
if ( ! first_frame )
return ;
/* Skip through the call chain to the specified starting frame */
while ( ! unwind_done ( state ) ) {
if ( on_stack ( & state - > stack_info , first_frame , sizeof ( struct stack_frame ) ) ) {
if ( state - > sp > = first_frame )
break ;
}
unwind_next_frame ( state ) ;
}
2019-01-28 08:33:08 +01:00
}
EXPORT_SYMBOL_GPL ( __unwind_start ) ;