#include <sys/cdefs.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/dtrace_impl.h>
#include <sys/kernel.h>
#include <sys/stack.h>
#include <sys/sysent.h>
#include <sys/pcpu.h>
#include <machine/frame.h>
#include <machine/md_var.h>
#include <machine/psl.h>
#include <machine/reg.h>
#include <machine/stack.h>
#include <vm/vm.h>
#include <vm/vm_param.h>
#include <vm/pmap.h>
#include "regset.h"
#define RETURN_OFFSET 4
#define RETURN_OFFSET64 16
#ifdef __powerpc64__
#define OFFSET 4
#define FRAME_OFFSET 48
#else
#define OFFSET 0
#define FRAME_OFFSET 8
#endif
#define INKERNEL(x) (((x) <= VM_MAX_KERNEL_ADDRESS && \
(x) >= VM_MIN_KERNEL_ADDRESS) || \
(PMAP_HAS_DMAP && (x) >= DMAP_BASE_ADDRESS && \
(x) <= DMAP_MAX_ADDRESS))
static __inline int
dtrace_sp_inkernel(uintptr_t sp)
{
struct trapframe *frame;
vm_offset_t callpc;
if (!INKERNEL(sp) || (sp & 0xf) != 0)
return (0);
#ifdef __powerpc64__
callpc = *(vm_offset_t *)(sp + RETURN_OFFSET64);
#else
callpc = *(vm_offset_t *)(sp + RETURN_OFFSET);
#endif
if ((callpc & 3) || (callpc < 0x100))
return (0);
if (callpc + OFFSET == (vm_offset_t) &trapexit ||
callpc + OFFSET == (vm_offset_t) &asttrapexit) {
frame = (struct trapframe *)(sp + FRAME_OFFSET);
return ((frame->srr1 & PSL_PR) == 0);
}
return (1);
}
static __inline void
dtrace_next_sp_pc(uintptr_t sp, uintptr_t *nsp, uintptr_t *pc, uintptr_t *lr)
{
vm_offset_t callpc;
struct trapframe *frame;
if (lr != 0 && *lr != 0)
callpc = *lr;
else
#ifdef __powerpc64__
callpc = *(vm_offset_t *)(sp + RETURN_OFFSET64);
#else
callpc = *(vm_offset_t *)(sp + RETURN_OFFSET);
#endif
if ((callpc + OFFSET == (vm_offset_t) &trapexit ||
callpc + OFFSET == (vm_offset_t) &asttrapexit)) {
frame = (struct trapframe *)(sp + FRAME_OFFSET);
if (nsp != NULL)
*nsp = frame->fixreg[1];
if (pc != NULL)
*pc = frame->srr0;
if (lr != NULL)
*lr = frame->lr;
return;
}
if (nsp != NULL)
*nsp = *(uintptr_t *)sp;
if (pc != NULL)
*pc = callpc;
if (lr != NULL)
*lr = 0;
}
void
dtrace_getpcstack(pc_t *pcstack, int pcstack_limit, int aframes,
uint32_t *intrpc)
{
int depth = 0;
uintptr_t osp, sp, lr = 0;
vm_offset_t callpc;
pc_t caller = (pc_t) solaris_cpu[curcpu].cpu_dtrace_caller;
osp = PAGE_SIZE;
if (intrpc != 0)
pcstack[depth++] = (pc_t) intrpc;
aframes++;
sp = (uintptr_t)__builtin_frame_address(0);
while (depth < pcstack_limit) {
if (sp <= osp)
break;
if (!dtrace_sp_inkernel(sp))
break;
osp = sp;
dtrace_next_sp_pc(osp, &sp, &callpc, &lr);
if (aframes > 0) {
aframes--;
if ((aframes == 0) && (caller != 0)) {
pcstack[depth++] = caller;
}
}
else {
pcstack[depth++] = callpc;
}
}
for (; depth < pcstack_limit; depth++) {
pcstack[depth] = 0;
}
}
static int
dtrace_getustack_common(uint64_t *pcstack, int pcstack_limit, uintptr_t pc,
uintptr_t sp)
{
proc_t *p = curproc;
int ret = 0;
ASSERT(pcstack == NULL || pcstack_limit > 0);
while (pc != 0) {
ret++;
if (pcstack != NULL) {
*pcstack++ = (uint64_t)pc;
pcstack_limit--;
if (pcstack_limit <= 0)
break;
}
if (sp == 0)
break;
if (SV_PROC_FLAG(p, SV_ILP32)) {
pc = dtrace_fuword32((void *)(sp + RETURN_OFFSET));
sp = dtrace_fuword32((void *)sp);
}
else {
pc = dtrace_fuword64((void *)(sp + RETURN_OFFSET64));
sp = dtrace_fuword64((void *)sp);
}
}
return (ret);
}
void
dtrace_getupcstack(uint64_t *pcstack, int pcstack_limit)
{
proc_t *p = curproc;
struct trapframe *tf;
uintptr_t pc, sp;
volatile uint16_t *flags =
(volatile uint16_t *)&cpu_core[curcpu].cpuc_dtrace_flags;
int n;
if (*flags & CPU_DTRACE_FAULT)
return;
if (pcstack_limit <= 0)
return;
if (p == NULL || (tf = curthread->td_frame) == NULL)
goto zero;
*pcstack++ = (uint64_t)p->p_pid;
pcstack_limit--;
if (pcstack_limit <= 0)
return;
pc = tf->srr0;
sp = tf->fixreg[1];
if (DTRACE_CPUFLAG_ISSET(CPU_DTRACE_ENTRY)) {
*pcstack++ = (uint64_t)pc;
pcstack_limit--;
if (pcstack_limit <= 0)
return;
pc = tf->lr;
}
n = dtrace_getustack_common(pcstack, pcstack_limit, pc, sp);
ASSERT(n >= 0);
ASSERT(n <= pcstack_limit);
pcstack += n;
pcstack_limit -= n;
zero:
while (pcstack_limit-- > 0)
*pcstack++ = 0;
}
int
dtrace_getustackdepth(void)
{
proc_t *p = curproc;
struct trapframe *tf;
uintptr_t pc, sp;
int n = 0;
if (p == NULL || (tf = curthread->td_frame) == NULL)
return (0);
if (DTRACE_CPUFLAG_ISSET(CPU_DTRACE_FAULT))
return (-1);
pc = tf->srr0;
sp = tf->fixreg[1];
if (DTRACE_CPUFLAG_ISSET(CPU_DTRACE_ENTRY)) {
if (SV_PROC_FLAG(p, SV_ILP32)) {
pc = dtrace_fuword32((void *) sp);
}
else
pc = dtrace_fuword64((void *) sp);
n++;
}
n += dtrace_getustack_common(NULL, 0, pc, sp);
return (n);
}
void
dtrace_getufpstack(uint64_t *pcstack, uint64_t *fpstack, int pcstack_limit)
{
proc_t *p = curproc;
struct trapframe *tf;
uintptr_t pc, sp;
volatile uint16_t *flags =
(volatile uint16_t *)&cpu_core[curcpu].cpuc_dtrace_flags;
#ifdef notyet
uintptr_t oldcontext;
size_t s1, s2;
#endif
if (*flags & CPU_DTRACE_FAULT)
return;
if (pcstack_limit <= 0)
return;
if (p == NULL || (tf = curthread->td_frame) == NULL)
goto zero;
*pcstack++ = (uint64_t)p->p_pid;
pcstack_limit--;
if (pcstack_limit <= 0)
return;
pc = tf->srr0;
sp = tf->fixreg[1];
#ifdef notyet
oldcontext = lwp->lwp_oldcontext;
s1 = sizeof (struct xframe) + 2 * sizeof (long);
s2 = s1 + sizeof (siginfo_t);
#endif
if (DTRACE_CPUFLAG_ISSET(CPU_DTRACE_ENTRY)) {
*pcstack++ = (uint64_t)pc;
*fpstack++ = 0;
pcstack_limit--;
if (pcstack_limit <= 0)
return;
if (SV_PROC_FLAG(p, SV_ILP32)) {
pc = dtrace_fuword32((void *)sp);
}
else {
pc = dtrace_fuword64((void *)sp);
}
}
while (pc != 0) {
*pcstack++ = (uint64_t)pc;
*fpstack++ = sp;
pcstack_limit--;
if (pcstack_limit <= 0)
break;
if (sp == 0)
break;
#ifdef notyet
if (oldcontext == sp + s1 || oldcontext == sp + s2) {
ucontext_t *ucp = (ucontext_t *)oldcontext;
greg_t *gregs = ucp->uc_mcontext.gregs;
sp = dtrace_fulword(&gregs[REG_FP]);
pc = dtrace_fulword(&gregs[REG_PC]);
oldcontext = dtrace_fulword(&ucp->uc_link);
} else
#endif
{
if (SV_PROC_FLAG(p, SV_ILP32)) {
pc = dtrace_fuword32((void *)(sp + RETURN_OFFSET));
sp = dtrace_fuword32((void *)sp);
}
else {
pc = dtrace_fuword64((void *)(sp + RETURN_OFFSET64));
sp = dtrace_fuword64((void *)sp);
}
}
if (*flags & CPU_DTRACE_FAULT) {
*flags &= ~CPU_DTRACE_FAULT;
break;
}
}
zero:
while (pcstack_limit-- > 0)
*pcstack++ = 0;
}
uint64_t
dtrace_getarg(int arg, int aframes)
{
uintptr_t val;
uintptr_t *fp = (uintptr_t *)__builtin_frame_address(0);
uintptr_t *stack;
int i;
int inreg = 7;
for (i = 1; i <= aframes; i++) {
fp = (uintptr_t *)*fp;
#ifdef __powerpc64__
if ((long)(fp[2]) + 4 == (long)trapexit) {
#else
if ((long)(fp[1]) == (long)trapexit) {
#endif
#ifdef __powerpc64__
struct reg *rp = (struct reg *)((uintptr_t)fp[0] + 48);
#else
struct reg *rp = (struct reg *)((uintptr_t)fp[0] + 8);
#endif
if (arg <= inreg) {
stack = &rp->fixreg[3];
} else {
stack = (uintptr_t *)(rp->fixreg[1]);
arg -= inreg;
}
goto load;
}
}
arg++;
if (arg <= inreg) {
DTRACE_CPUFLAG_SET(CPU_DTRACE_ILLOP);
return (0);
}
arg -= (inreg + 1);
stack = fp + 2;
load:
DTRACE_CPUFLAG_SET(CPU_DTRACE_NOFAULT);
val = stack[arg];
DTRACE_CPUFLAG_CLEAR(CPU_DTRACE_NOFAULT);
return (val);
}
int
dtrace_getstackdepth(int aframes)
{
int depth = 0;
uintptr_t osp, sp;
vm_offset_t callpc;
osp = PAGE_SIZE;
sp = (uintptr_t)__builtin_frame_address(0);
for(;;) {
if (sp <= osp)
break;
if (!dtrace_sp_inkernel(sp))
break;
depth++;
osp = sp;
dtrace_next_sp_pc(sp, &sp, NULL, NULL);
}
if (depth < aframes)
return (0);
return (depth - aframes);
}
ulong_t
dtrace_getreg(struct trapframe *frame, uint_t reg)
{
if (reg < 32)
return (frame->fixreg[reg]);
switch (reg) {
case 32:
return (frame->lr);
case 33:
return (frame->cr);
case 34:
return (frame->xer);
case 35:
return (frame->ctr);
case 36:
return (frame->srr0);
case 37:
return (frame->srr1);
case 38:
return (frame->exc);
default:
DTRACE_CPUFLAG_SET(CPU_DTRACE_ILLOP);
return (0);
}
}
static int
dtrace_copycheck(uintptr_t uaddr, uintptr_t kaddr, size_t size)
{
ASSERT(INKERNEL(kaddr) && kaddr + size >= kaddr);
if (uaddr + size > VM_MAXUSER_ADDRESS || uaddr + size < uaddr) {
DTRACE_CPUFLAG_SET(CPU_DTRACE_BADADDR);
cpu_core[curcpu].cpuc_dtrace_illval = uaddr;
return (0);
}
return (1);
}
void
dtrace_copyin(uintptr_t uaddr, uintptr_t kaddr, size_t size,
volatile uint16_t *flags)
{
if (dtrace_copycheck(uaddr, kaddr, size))
if (copyin((const void *)uaddr, (void *)kaddr, size)) {
DTRACE_CPUFLAG_SET(CPU_DTRACE_BADADDR);
cpu_core[curcpu].cpuc_dtrace_illval = (uintptr_t)uaddr;
}
}
void
dtrace_copyout(uintptr_t kaddr, uintptr_t uaddr, size_t size,
volatile uint16_t *flags)
{
if (dtrace_copycheck(uaddr, kaddr, size)) {
if (copyout((const void *)kaddr, (void *)uaddr, size)) {
DTRACE_CPUFLAG_SET(CPU_DTRACE_BADADDR);
cpu_core[curcpu].cpuc_dtrace_illval = (uintptr_t)uaddr;
}
}
}
void
dtrace_copyinstr(uintptr_t uaddr, uintptr_t kaddr, size_t size,
volatile uint16_t *flags)
{
size_t actual;
int error;
if (dtrace_copycheck(uaddr, kaddr, size)) {
error = copyinstr((const void *)uaddr, (void *)kaddr,
size, &actual);
if (error && error != ENAMETOOLONG) {
DTRACE_CPUFLAG_SET(CPU_DTRACE_BADADDR);
cpu_core[curcpu].cpuc_dtrace_illval = (uintptr_t)uaddr;
}
}
}
void
dtrace_copyoutstr(uintptr_t kaddr, uintptr_t uaddr, size_t size,
volatile uint16_t *flags)
{
size_t len;
if (dtrace_copycheck(uaddr, kaddr, size)) {
len = strlen((const char *)kaddr);
if (len > size)
len = size;
if (copyout((const void *)kaddr, (void *)uaddr, len)) {
DTRACE_CPUFLAG_SET(CPU_DTRACE_BADADDR);
cpu_core[curcpu].cpuc_dtrace_illval = (uintptr_t)uaddr;
}
}
}
uint8_t
dtrace_fuword8(void *uaddr)
{
if ((uintptr_t)uaddr > VM_MAXUSER_ADDRESS) {
DTRACE_CPUFLAG_SET(CPU_DTRACE_BADADDR);
cpu_core[curcpu].cpuc_dtrace_illval = (uintptr_t)uaddr;
return (0);
}
return (fubyte(uaddr));
}
uint16_t
dtrace_fuword16(void *uaddr)
{
uint16_t ret = 0;
if (dtrace_copycheck((uintptr_t)uaddr, (uintptr_t)&ret, sizeof(ret))) {
if (copyin((const void *)uaddr, (void *)&ret, sizeof(ret))) {
DTRACE_CPUFLAG_SET(CPU_DTRACE_BADADDR);
cpu_core[curcpu].cpuc_dtrace_illval = (uintptr_t)uaddr;
}
}
return ret;
}
uint32_t
dtrace_fuword32(void *uaddr)
{
if ((uintptr_t)uaddr > VM_MAXUSER_ADDRESS) {
DTRACE_CPUFLAG_SET(CPU_DTRACE_BADADDR);
cpu_core[curcpu].cpuc_dtrace_illval = (uintptr_t)uaddr;
return (0);
}
return (fuword32(uaddr));
}
uint64_t
dtrace_fuword64(void *uaddr)
{
uint64_t ret = 0;
if (dtrace_copycheck((uintptr_t)uaddr, (uintptr_t)&ret, sizeof(ret))) {
if (copyin((const void *)uaddr, (void *)&ret, sizeof(ret))) {
DTRACE_CPUFLAG_SET(CPU_DTRACE_BADADDR);
cpu_core[curcpu].cpuc_dtrace_illval = (uintptr_t)uaddr;
}
}
return ret;
}
uintptr_t
dtrace_fulword(void *uaddr)
{
uintptr_t ret = 0;
if (dtrace_copycheck((uintptr_t)uaddr, (uintptr_t)&ret, sizeof(ret))) {
if (copyin((const void *)uaddr, (void *)&ret, sizeof(ret))) {
DTRACE_CPUFLAG_SET(CPU_DTRACE_BADADDR);
cpu_core[curcpu].cpuc_dtrace_illval = (uintptr_t)uaddr;
}
}
return ret;
}