97 lines
2.1 KiB
C
97 lines
2.1 KiB
C
/*
|
|
* Perf callchain handling code.
|
|
*
|
|
* Based on the ARM perf implementation.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/perf_event.h>
|
|
#include <linux/uaccess.h>
|
|
#include <asm/ptrace.h>
|
|
#include <asm/stacktrace.h>
|
|
|
|
static bool is_valid_call(unsigned long calladdr)
|
|
{
|
|
unsigned int callinsn;
|
|
|
|
/* Check the possible return address is aligned. */
|
|
if (!(calladdr & 0x3)) {
|
|
if (!get_user(callinsn, (unsigned int *)calladdr)) {
|
|
/* Check for CALLR or SWAP PC,D1RtP. */
|
|
if ((callinsn & 0xff000000) == 0xab000000 ||
|
|
callinsn == 0xa3200aa0)
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static struct metag_frame __user *
|
|
user_backtrace(struct metag_frame __user *user_frame,
|
|
struct perf_callchain_entry *entry)
|
|
{
|
|
struct metag_frame frame;
|
|
unsigned long calladdr;
|
|
|
|
/* We cannot rely on having frame pointers in user code. */
|
|
while (1) {
|
|
/* Also check accessibility of one struct frame beyond */
|
|
if (!access_ok(VERIFY_READ, user_frame, sizeof(frame)))
|
|
return 0;
|
|
if (__copy_from_user_inatomic(&frame, user_frame,
|
|
sizeof(frame)))
|
|
return 0;
|
|
|
|
--user_frame;
|
|
|
|
calladdr = frame.lr - 4;
|
|
if (is_valid_call(calladdr)) {
|
|
perf_callchain_store(entry, calladdr);
|
|
return user_frame;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
perf_callchain_user(struct perf_callchain_entry *entry, struct pt_regs *regs)
|
|
{
|
|
unsigned long sp = regs->ctx.AX[0].U0;
|
|
struct metag_frame __user *frame;
|
|
|
|
frame = (struct metag_frame __user *)sp;
|
|
|
|
--frame;
|
|
|
|
while ((entry->nr < PERF_MAX_STACK_DEPTH) && frame)
|
|
frame = user_backtrace(frame, 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)
|
|
{
|
|
struct perf_callchain_entry *entry = data;
|
|
perf_callchain_store(entry, fr->pc);
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
perf_callchain_kernel(struct perf_callchain_entry *entry, struct pt_regs *regs)
|
|
{
|
|
struct stackframe fr;
|
|
|
|
fr.fp = regs->ctx.AX[1].U0;
|
|
fr.sp = regs->ctx.AX[0].U0;
|
|
fr.lr = regs->ctx.DX[4].U1;
|
|
fr.pc = regs->ctx.CurrPC;
|
|
walk_stackframe(&fr, callchain_trace, entry);
|
|
}
|