2014-09-29 20:15:32 +04:00
/*
* ARM callchain support
*
* Copyright ( C ) 2009 picoChip Designs , Ltd . , Jamie Iles
* Copyright ( C ) 2010 ARM Ltd . , Will Deacon < will . deacon @ arm . com >
*
* This code is based on the ARM OProfile backtrace code .
*/
# include <linux/perf_event.h>
# include <linux/uaccess.h>
# include <asm/stacktrace.h>
/*
* The registers we ' re interested in are at the end of the variable
* length saved register structure . The fp points at the end of this
* structure so the address of this struct is :
* ( struct frame_tail * ) ( xxx - > fp ) - 1
*
* This code has been adapted from the ARM OProfile support .
*/
struct frame_tail {
struct frame_tail __user * fp ;
unsigned long sp ;
unsigned long lr ;
} __attribute__ ( ( packed ) ) ;
/*
* Get the return address for a single stackframe and return a pointer to the
* next frame tail .
*/
static struct frame_tail __user *
user_backtrace ( struct frame_tail __user * tail ,
2016-04-28 18:30:53 +03:00
struct perf_callchain_entry_ctx * entry )
2014-09-29 20:15:32 +04:00
{
struct frame_tail buftail ;
unsigned long err ;
if ( ! access_ok ( VERIFY_READ , tail , sizeof ( buftail ) ) )
return NULL ;
pagefault_disable ( ) ;
err = __copy_from_user_inatomic ( & buftail , tail , sizeof ( buftail ) ) ;
pagefault_enable ( ) ;
if ( err )
return NULL ;
perf_callchain_store ( entry , buftail . lr ) ;
/*
* Frame pointers should strictly progress back up the stack
* ( towards higher addresses ) .
*/
if ( tail + 1 > = buftail . fp )
return NULL ;
return buftail . fp - 1 ;
}
void
2016-04-28 18:30:53 +03:00
perf_callchain_user ( struct perf_callchain_entry_ctx * entry , struct pt_regs * regs )
2014-09-29 20:15:32 +04:00
{
struct frame_tail __user * tail ;
if ( perf_guest_cbs & & perf_guest_cbs - > is_in_guest ( ) ) {
/* We don't support guest os callchain now */
return ;
}
perf_callchain_store ( entry , regs - > ARM_pc ) ;
if ( ! current - > mm )
return ;
tail = ( struct frame_tail __user * ) regs - > ARM_fp - 1 ;
perf core: Add a 'nr' field to perf_event_callchain_context
We will use it to count how many addresses are in the entry->ip[] array,
excluding PERF_CONTEXT_{KERNEL,USER,etc} entries, so that we can really
return the number of entries specified by the user via the relevant
sysctl, kernel.perf_event_max_contexts, or via the per event
perf_event_attr.sample_max_stack knob.
This way we keep the perf_sample->ip_callchain->nr meaning, that is the
number of entries, be it real addresses or PERF_CONTEXT_ entries, while
honouring the max_stack knobs, i.e. the end result will be max_stack
entries if we have at least that many entries in a given stack trace.
Cc: David Ahern <dsahern@gmail.com>
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Jiri Olsa <jolsa@redhat.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Link: http://lkml.kernel.org/n/tip-s8teto51tdqvlfhefndtat9r@git.kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2016-05-11 00:08:32 +03:00
while ( ( entry - > nr < entry - > max_stack ) & &
2014-09-29 20:15:32 +04:00
tail & & ! ( ( unsigned long ) tail & 0x3 ) )
tail = user_backtrace ( tail , entry ) ;
}
/*
* Gets called by walk_stackframe ( ) for every stackframe . This will be called
* whist unwinding the stackframe and is like a subroutine return so we use
* the PC .
*/
static int
callchain_trace ( struct stackframe * fr ,
void * data )
{
2016-04-28 18:30:53 +03:00
struct perf_callchain_entry_ctx * entry = data ;
2014-09-29 20:15:32 +04:00
perf_callchain_store ( entry , fr - > pc ) ;
return 0 ;
}
void
2016-04-28 18:30:53 +03:00
perf_callchain_kernel ( struct perf_callchain_entry_ctx * entry , struct pt_regs * regs )
2014-09-29 20:15:32 +04:00
{
struct stackframe fr ;
if ( perf_guest_cbs & & perf_guest_cbs - > is_in_guest ( ) ) {
/* We don't support guest os callchain now */
return ;
}
arm_get_current_stackframe ( regs , & fr ) ;
walk_stackframe ( & fr , callchain_trace , entry ) ;
}
unsigned long perf_instruction_pointer ( struct pt_regs * regs )
{
if ( perf_guest_cbs & & perf_guest_cbs - > is_in_guest ( ) )
return perf_guest_cbs - > get_guest_ip ( ) ;
return instruction_pointer ( regs ) ;
}
unsigned long perf_misc_flags ( struct pt_regs * regs )
{
int misc = 0 ;
if ( perf_guest_cbs & & perf_guest_cbs - > is_in_guest ( ) ) {
if ( perf_guest_cbs - > is_user_mode ( ) )
misc | = PERF_RECORD_MISC_GUEST_USER ;
else
misc | = PERF_RECORD_MISC_GUEST_KERNEL ;
} else {
if ( user_mode ( regs ) )
misc | = PERF_RECORD_MISC_USER ;
else
misc | = PERF_RECORD_MISC_KERNEL ;
}
return misc ;
}