2012-03-05 11:49:27 +00:00
/*
* Stack tracing support
*
* Copyright ( C ) 2012 ARM Ltd .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program . If not , see < http : //www.gnu.org/licenses/>.
*/
# include <linux/kernel.h>
# include <linux/export.h>
2015-12-15 17:33:41 +09:00
# include <linux/ftrace.h>
2012-03-05 11:49:27 +00:00
# include <linux/sched.h>
# include <linux/stacktrace.h>
2015-12-04 11:02:26 +00:00
# include <asm/irq.h>
2012-03-05 11:49:27 +00:00
# include <asm/stacktrace.h>
/*
* AArch64 PCS assigns the frame pointer to x29 .
*
* A simple function prologue looks like this :
* sub sp , sp , # 0x10
* stp x29 , x30 , [ sp ]
* mov x29 , sp
*
* A simple function epilogue looks like this :
* mov sp , x29
* ldp x29 , x30 , [ sp ]
* add sp , sp , # 0x10
*/
2015-12-15 17:33:40 +09:00
int notrace unwind_frame ( struct task_struct * tsk , struct stackframe * frame )
2012-03-05 11:49:27 +00:00
{
unsigned long high , low ;
unsigned long fp = frame - > fp ;
2015-12-04 11:02:26 +00:00
unsigned long irq_stack_ptr ;
/*
2016-02-11 13:53:10 -08:00
* Switching between stacks is valid when tracing current and in
* non - preemptible context .
2015-12-04 11:02:26 +00:00
*/
2016-02-11 13:53:10 -08:00
if ( tsk = = current & & ! preemptible ( ) )
irq_stack_ptr = IRQ_STACK_PTR ( smp_processor_id ( ) ) ;
else
irq_stack_ptr = 0 ;
2012-03-05 11:49:27 +00:00
low = frame - > sp ;
2015-12-04 11:02:26 +00:00
/* irq stacks are not THREAD_SIZE aligned */
if ( on_irq_stack ( frame - > sp , raw_smp_processor_id ( ) ) )
high = irq_stack_ptr ;
else
high = ALIGN ( low , THREAD_SIZE ) - 0x20 ;
2012-03-05 11:49:27 +00:00
2015-12-04 11:02:26 +00:00
if ( fp < low | | fp > high | | fp & 0xf )
2012-03-05 11:49:27 +00:00
return - EINVAL ;
frame - > sp = fp + 0x10 ;
2016-02-08 09:13:09 -08:00
frame - > fp = READ_ONCE_NOCHECK ( * ( unsigned long * ) ( fp ) ) ;
frame - > pc = READ_ONCE_NOCHECK ( * ( unsigned long * ) ( fp + 8 ) ) ;
2012-03-05 11:49:27 +00:00
2015-12-15 17:33:41 +09:00
# ifdef CONFIG_FUNCTION_GRAPH_TRACER
if ( tsk & & tsk - > ret_stack & &
( frame - > pc = = ( unsigned long ) return_to_handler ) ) {
/*
* This is a case where function graph tracer has
* modified a return address ( LR ) in a stack frame
* to hook a function return .
* So replace it to an original value .
*/
frame - > pc = tsk - > ret_stack [ frame - > graph - - ] . ret ;
}
# endif /* CONFIG_FUNCTION_GRAPH_TRACER */
2015-12-04 11:02:26 +00:00
/*
* Check whether we are going to walk through from interrupt stack
* to task stack .
* If we reach the end of the stack - and its an interrupt stack ,
2015-12-15 11:21:25 +00:00
* unpack the dummy frame to find the original elr .
2015-12-10 10:22:40 +00:00
*
* Check the frame - > fp we read from the bottom of the irq_stack ,
* and the original task stack pointer are both in current - > stack .
2015-12-04 11:02:26 +00:00
*/
2015-12-10 10:22:40 +00:00
if ( frame - > sp = = irq_stack_ptr ) {
2015-12-15 11:21:25 +00:00
struct pt_regs * irq_args ;
2015-12-10 10:22:40 +00:00
unsigned long orig_sp = IRQ_STACK_TO_TASK_STACK ( irq_stack_ptr ) ;
2015-12-15 11:21:25 +00:00
if ( object_is_on_stack ( ( void * ) orig_sp ) & &
object_is_on_stack ( ( void * ) frame - > fp ) ) {
2015-12-10 10:22:40 +00:00
frame - > sp = orig_sp ;
2015-12-15 11:21:25 +00:00
/* orig_sp is the saved pt_regs, find the elr */
irq_args = ( struct pt_regs * ) orig_sp ;
frame - > pc = irq_args - > pc ;
} else {
/*
* This frame has a non - standard format , and we
* didn ' t fix it , because the data looked wrong .
* Refuse to output this frame .
*/
return - EINVAL ;
}
2015-12-10 10:22:40 +00:00
}
2015-12-04 11:02:26 +00:00
2012-03-05 11:49:27 +00:00
return 0 ;
}
2015-12-15 17:33:40 +09:00
void notrace walk_stackframe ( struct task_struct * tsk , struct stackframe * frame ,
2012-03-05 11:49:27 +00:00
int ( * fn ) ( struct stackframe * , void * ) , void * data )
{
while ( 1 ) {
int ret ;
if ( fn ( frame , data ) )
break ;
2015-12-15 17:33:40 +09:00
ret = unwind_frame ( tsk , frame ) ;
2012-03-05 11:49:27 +00:00
if ( ret < 0 )
break ;
}
}
EXPORT_SYMBOL ( walk_stackframe ) ;
# ifdef CONFIG_STACKTRACE
struct stack_trace_data {
struct stack_trace * trace ;
unsigned int no_sched_functions ;
unsigned int skip ;
} ;
static int save_trace ( struct stackframe * frame , void * d )
{
struct stack_trace_data * data = d ;
struct stack_trace * trace = data - > trace ;
unsigned long addr = frame - > pc ;
if ( data - > no_sched_functions & & in_sched_functions ( addr ) )
return 0 ;
if ( data - > skip ) {
data - > skip - - ;
return 0 ;
}
trace - > entries [ trace - > nr_entries + + ] = addr ;
return trace - > nr_entries > = trace - > max_entries ;
}
void save_stack_trace_tsk ( struct task_struct * tsk , struct stack_trace * trace )
{
struct stack_trace_data data ;
struct stackframe frame ;
data . trace = trace ;
data . skip = trace - > skip ;
if ( tsk ! = current ) {
data . no_sched_functions = 1 ;
frame . fp = thread_saved_fp ( tsk ) ;
frame . sp = thread_saved_sp ( tsk ) ;
frame . pc = thread_saved_pc ( tsk ) ;
} else {
data . no_sched_functions = 0 ;
frame . fp = ( unsigned long ) __builtin_frame_address ( 0 ) ;
2014-08-27 05:29:30 +01:00
frame . sp = current_stack_pointer ;
2012-03-05 11:49:27 +00:00
frame . pc = ( unsigned long ) save_stack_trace_tsk ;
}
2015-12-15 17:33:41 +09:00
# ifdef CONFIG_FUNCTION_GRAPH_TRACER
frame . graph = tsk - > curr_ret_stack ;
# endif
2012-03-05 11:49:27 +00:00
2015-12-15 17:33:40 +09:00
walk_stackframe ( tsk , & frame , save_trace , & data ) ;
2012-03-05 11:49:27 +00:00
if ( trace - > nr_entries < trace - > max_entries )
trace - > entries [ trace - > nr_entries + + ] = ULONG_MAX ;
}
void save_stack_trace ( struct stack_trace * trace )
{
save_stack_trace_tsk ( current , trace ) ;
}
EXPORT_SYMBOL_GPL ( save_stack_trace ) ;
# endif