#include <sys/dtrace_impl.h>
#include <sys/atomic.h>
#include <sys/model.h>
#include <sys/frame.h>
#include <sys/stack.h>
#include <sys/machpcb.h>
#include <sys/procfs_isa.h>
#include <sys/cmn_err.h>
#include <sys/sysmacros.h>
#define DTRACE_FMT3OP3_MASK 0x81000000
#define DTRACE_FMT3OP3 0x80000000
#define DTRACE_FMT3RS1_SHIFT 14
#define DTRACE_FMT3RD_SHIFT 25
#define DTRACE_DISP22_SHIFT 10
#define DTRACE_RMASK 0x1f
#define DTRACE_REG_L0 16
#define DTRACE_REG_O7 15
#define DTRACE_REG_I0 24
#define DTRACE_REG_I6 30
#define DTRACE_RET 0x81c7e008
#define DTRACE_RETL 0x81c3e008
#define DTRACE_SAVE_MASK 0xc1f80000
#define DTRACE_SAVE 0x81e00000
#define DTRACE_RESTORE 0x81e80000
#define DTRACE_CALL_MASK 0xc0000000
#define DTRACE_CALL 0x40000000
#define DTRACE_JMPL_MASK 0x81f80000
#define DTRACE_JMPL 0x81c00000
#define DTRACE_BA_MASK 0xdfc00000
#define DTRACE_BA 0x10800000
#define DTRACE_BA_MAX 10
extern int dtrace_getupcstack_top(uint64_t *, int, uintptr_t *);
extern int dtrace_getustackdepth_top(uintptr_t *);
extern ulong_t dtrace_getreg_win(uint_t, uint_t);
extern void dtrace_putreg_win(uint_t, ulong_t);
extern int dtrace_fish(int, int, uintptr_t *);
int dtrace_ustackdepth_max = 2048;
void
dtrace_getpcstack(pc_t *pcstack, int pcstack_limit, int aframes, uint32_t *pc)
{
struct frame *fp, *nextfp, *minfp, *stacktop;
int depth = 0;
int on_intr, j = 0;
uint32_t i, r;
fp = (struct frame *)((caddr_t)dtrace_getfp() + STACK_BIAS);
dtrace_flush_windows();
if (pc != NULL) {
int delay = 0, branches = 0, taken = 0;
if (depth < pcstack_limit)
pcstack[depth++] = (pc_t)(uintptr_t)pc;
DTRACE_CPUFLAG_SET(CPU_DTRACE_NOFAULT);
for (;;) {
i = pc[j++];
if ((i & DTRACE_FMT3OP3_MASK) == DTRACE_FMT3OP3) {
r = (i >> DTRACE_FMT3RS1_SHIFT) & DTRACE_RMASK;
if (r >= DTRACE_REG_L0)
goto nonleaf;
r = (i >> DTRACE_FMT3RD_SHIFT) & DTRACE_RMASK;
if (r >= DTRACE_REG_L0)
goto nonleaf;
if ((i & DTRACE_JMPL_MASK) == DTRACE_JMPL) {
delay = 1;
continue;
}
if (r == DTRACE_REG_O7) {
if (delay)
goto leaf;
i &= DTRACE_JMPL_MASK;
if (i == DTRACE_JMPL) {
delay = 1;
continue;
}
goto nonleaf;
}
} else {
if ((i & DTRACE_CALL_MASK) == DTRACE_CALL) {
delay = 1;
continue;
}
if (i == DTRACE_RET)
goto nonleaf;
if (i == DTRACE_RETL)
goto leaf;
if ((i & DTRACE_BA_MASK) == DTRACE_BA) {
if (++branches == DTRACE_BA_MAX ||
taken == j)
goto nonleaf;
taken = j;
j += ((int)(i << DTRACE_DISP22_SHIFT) >>
DTRACE_DISP22_SHIFT) - 1;
continue;
}
if ((i & DTRACE_SAVE_MASK) == DTRACE_SAVE)
goto leaf;
if ((i & DTRACE_SAVE_MASK) == DTRACE_RESTORE)
goto nonleaf;
}
if (delay) {
goto nonleaf;
}
}
nonleaf:
aframes++;
leaf:
DTRACE_CPUFLAG_CLEAR(CPU_DTRACE_NOFAULT);
}
if ((on_intr = CPU_ON_INTR(CPU)) != 0)
stacktop = (struct frame *)(CPU->cpu_intr_stack + SA(MINFRAME));
else
stacktop = (struct frame *)curthread->t_stk;
minfp = fp;
while (depth < pcstack_limit) {
nextfp = (struct frame *)((caddr_t)fp->fr_savfp + STACK_BIAS);
if (nextfp <= minfp || nextfp >= stacktop) {
if (!on_intr && nextfp == stacktop && aframes != 0) {
ASSERT(aframes == 1);
ASSERT(depth == 0);
while (depth < pcstack_limit)
pcstack[depth++] = 0;
return;
}
if (on_intr) {
stacktop = (struct frame *)curthread->t_stk;
minfp = (struct frame *)curthread->t_stkbase;
on_intr = 0;
if (nextfp > minfp && nextfp < stacktop)
continue;
} else {
ASSERT(aframes == 0 ||
dtrace_getipl() > DISP_LEVEL);
pcstack[depth++] = (pc_t)fp->fr_savpc;
}
while (depth < pcstack_limit)
pcstack[depth++] = 0;
return;
}
if (aframes > 0) {
aframes--;
} else {
pcstack[depth++] = (pc_t)fp->fr_savpc;
}
fp = nextfp;
minfp = fp;
}
}
static int
dtrace_getustack_common(uint64_t *pcstack, int pcstack_limit, uintptr_t sp)
{
proc_t *p = curproc;
int ret = 0;
uintptr_t oldsp;
volatile uint16_t *flags =
(volatile uint16_t *)&cpu_core[CPU->cpu_id].cpuc_dtrace_flags;
ASSERT(pcstack == NULL || pcstack_limit > 0);
ASSERT(dtrace_ustackdepth_max > 0);
if (p->p_model == DATAMODEL_NATIVE) {
for (;;) {
struct frame *fr = (struct frame *)(sp + STACK_BIAS);
uintptr_t pc;
if (sp == 0 || fr == NULL ||
!IS_P2ALIGNED((uintptr_t)fr, STACK_ALIGN))
break;
oldsp = sp;
pc = dtrace_fulword(&fr->fr_savpc);
sp = dtrace_fulword(&fr->fr_savfp);
if (pc == 0)
break;
if (sp == oldsp || ret++ >= dtrace_ustackdepth_max) {
*flags |= CPU_DTRACE_BADSTACK;
cpu_core[CPU->cpu_id].cpuc_dtrace_illval = sp;
break;
}
if (pcstack != NULL) {
*pcstack++ = pc;
pcstack_limit--;
if (pcstack_limit == 0)
break;
}
}
} else {
sp = (uint32_t)sp;
for (;;) {
struct frame32 *fr = (struct frame32 *)sp;
uint32_t pc;
if (sp == 0 ||
!IS_P2ALIGNED((uintptr_t)fr, STACK_ALIGN32))
break;
oldsp = sp;
pc = dtrace_fuword32(&fr->fr_savpc);
sp = dtrace_fuword32(&fr->fr_savfp);
if (pc == 0)
break;
if (sp == oldsp || ret++ >= dtrace_ustackdepth_max) {
*flags |= CPU_DTRACE_BADSTACK;
cpu_core[CPU->cpu_id].cpuc_dtrace_illval = sp;
break;
}
if (pcstack != NULL) {
*pcstack++ = pc;
pcstack_limit--;
if (pcstack_limit == 0)
break;
}
}
}
return (ret);
}
void
dtrace_getupcstack(uint64_t *pcstack, int pcstack_limit)
{
klwp_t *lwp = ttolwp(curthread);
proc_t *p = curproc;
struct regs *rp;
uintptr_t sp;
int n;
ASSERT(DTRACE_CPUFLAG_ISSET(CPU_DTRACE_NOFAULT));
if (pcstack_limit <= 0)
return;
if (lwp == NULL || p == NULL || (rp = lwp->lwp_regs) == NULL)
goto zero;
*pcstack++ = (uint64_t)p->p_pid;
pcstack_limit--;
if (pcstack_limit <= 0)
return;
*pcstack++ = (uint64_t)rp->r_pc;
pcstack_limit--;
if (pcstack_limit <= 0)
return;
if (DTRACE_CPUFLAG_ISSET(CPU_DTRACE_ENTRY)) {
*pcstack++ = (uint64_t)rp->r_o7;
pcstack_limit--;
if (pcstack_limit <= 0)
return;
}
sp = rp->r_sp;
n = dtrace_getupcstack_top(pcstack, pcstack_limit, &sp);
ASSERT(n >= 0);
ASSERT(n <= pcstack_limit);
pcstack += n;
pcstack_limit -= n;
if (pcstack_limit <= 0)
return;
n = dtrace_getustack_common(pcstack, pcstack_limit, sp);
ASSERT(n >= 0);
ASSERT(n <= pcstack_limit);
pcstack += n;
pcstack_limit -= n;
zero:
while (pcstack_limit-- > 0)
*pcstack++ = 0;
}
int
dtrace_getustackdepth(void)
{
klwp_t *lwp = ttolwp(curthread);
proc_t *p = curproc;
struct regs *rp;
uintptr_t sp;
int n = 1;
if (lwp == NULL || p == NULL || (rp = lwp->lwp_regs) == NULL)
return (0);
if (DTRACE_CPUFLAG_ISSET(CPU_DTRACE_FAULT))
return (-1);
sp = rp->r_sp;
n += dtrace_getustackdepth_top(&sp);
n += dtrace_getustack_common(NULL, 0, sp);
if (DTRACE_CPUFLAG_ISSET(CPU_DTRACE_ENTRY) &&
(rp->r_o7 != 0 || n != 1))
n++;
return (n);
}
void
dtrace_getufpstack(uint64_t *pcstack, uint64_t *fpstack, int pcstack_limit)
{
klwp_t *lwp = ttolwp(curthread);
proc_t *p = ttoproc(curthread);
struct regs *rp;
uintptr_t sp;
if (pcstack_limit <= 0)
return;
if (lwp == NULL || p == NULL || (rp = lwp->lwp_regs) == NULL)
goto zero;
*pcstack++ = (uint64_t)p->p_pid;
pcstack_limit--;
if (pcstack_limit <= 0)
return;
if (DTRACE_CPUFLAG_ISSET(CPU_DTRACE_ENTRY)) {
*fpstack++ = 0;
*pcstack++ = (uint64_t)rp->r_pc;
pcstack_limit--;
if (pcstack_limit <= 0)
return;
*fpstack++ = (uint64_t)rp->r_sp;
*pcstack++ = (uint64_t)rp->r_o7;
pcstack_limit--;
} else {
*fpstack++ = (uint64_t)rp->r_sp;
*pcstack++ = (uint64_t)rp->r_pc;
pcstack_limit--;
}
if (pcstack_limit <= 0)
return;
sp = rp->r_sp;
dtrace_flush_user_windows();
if (p->p_model == DATAMODEL_NATIVE) {
while (pcstack_limit > 0) {
struct frame *fr = (struct frame *)(sp + STACK_BIAS);
uintptr_t pc;
if (sp == 0 || fr == NULL ||
((uintptr_t)&fr->fr_savpc & 3) != 0 ||
((uintptr_t)&fr->fr_savfp & 3) != 0)
break;
pc = dtrace_fulword(&fr->fr_savpc);
sp = dtrace_fulword(&fr->fr_savfp);
if (pc == 0)
break;
*fpstack++ = sp;
*pcstack++ = pc;
pcstack_limit--;
}
} else {
sp = (uint32_t)sp;
while (pcstack_limit > 0) {
struct frame32 *fr = (struct frame32 *)sp;
uint32_t pc;
if (sp == 0 ||
((uintptr_t)&fr->fr_savpc & 3) != 0 ||
((uintptr_t)&fr->fr_savfp & 3) != 0)
break;
pc = dtrace_fuword32(&fr->fr_savpc);
sp = dtrace_fuword32(&fr->fr_savfp);
if (pc == 0)
break;
*fpstack++ = sp;
*pcstack++ = pc;
pcstack_limit--;
}
}
zero:
while (pcstack_limit-- > 0)
*pcstack++ = 0;
}
uint64_t
dtrace_getarg(int arg, int aframes)
{
uintptr_t val;
struct frame *fp;
uint64_t rval;
aframes++;
if (arg < 6) {
if (dtrace_fish(aframes, DTRACE_REG_I0 + arg, &val) == 0)
return (val);
} else {
if (dtrace_fish(aframes, DTRACE_REG_I6, &val) == 0) {
fp = (struct frame *)(val + STACK_BIAS);
DTRACE_CPUFLAG_SET(CPU_DTRACE_NOFAULT);
rval = fp->fr_argx[arg - 6];
DTRACE_CPUFLAG_CLEAR(CPU_DTRACE_NOFAULT);
return (rval);
}
}
fp = (struct frame *)((caddr_t)dtrace_getfp() + STACK_BIAS);
dtrace_flush_windows();
DTRACE_CPUFLAG_SET(CPU_DTRACE_NOFAULT);
for (aframes -= 1; aframes; aframes--)
fp = (struct frame *)((caddr_t)fp->fr_savfp + STACK_BIAS);
if (arg < 6) {
rval = fp->fr_arg[arg];
} else {
fp = (struct frame *)((caddr_t)fp->fr_savfp + STACK_BIAS);
rval = fp->fr_argx[arg - 6];
}
DTRACE_CPUFLAG_CLEAR(CPU_DTRACE_NOFAULT);
return (rval);
}
int
dtrace_getstackdepth(int aframes)
{
struct frame *fp, *nextfp, *minfp, *stacktop;
int depth = 0;
int on_intr;
fp = (struct frame *)((caddr_t)dtrace_getfp() + STACK_BIAS);
dtrace_flush_windows();
if ((on_intr = CPU_ON_INTR(CPU)) != 0)
stacktop = (struct frame *)CPU->cpu_intr_stack + SA(MINFRAME);
else
stacktop = (struct frame *)curthread->t_stk;
minfp = fp;
for (;;) {
nextfp = (struct frame *)((caddr_t)fp->fr_savfp + STACK_BIAS);
if (nextfp <= minfp || nextfp >= stacktop) {
if (on_intr) {
stacktop = (struct frame *)curthread->t_stk;
minfp = (struct frame *)curthread->t_stkbase;
on_intr = 0;
continue;
}
return (++depth);
}
if (aframes > 0) {
aframes--;
} else {
depth++;
}
fp = nextfp;
minfp = fp;
}
}
ulong_t
dtrace_getreg(struct regs *rp, uint_t reg)
{
ulong_t value;
uintptr_t fp;
struct machpcb *mpcb;
if (reg == R_G0)
return (0);
if (reg <= R_G7)
return ((&rp->r_g1)[reg - 1]);
if (reg > R_I7) {
switch (reg) {
case R_CCR:
return ((rp->r_tstate >> TSTATE_CCR_SHIFT) &
TSTATE_CCR_MASK);
case R_PC:
return (rp->r_pc);
case R_nPC:
return (rp->r_npc);
case R_Y:
return (rp->r_y);
case R_ASI:
return ((rp->r_tstate >> TSTATE_ASI_SHIFT) &
TSTATE_ASI_MASK);
case R_FPRS:
return (dtrace_getfprs());
default:
DTRACE_CPUFLAG_SET(CPU_DTRACE_ILLOP);
return (0);
}
}
if (DTRACE_CPUFLAG_ISSET(CPU_DTRACE_FAKERESTORE)) {
if (reg > R_O7)
goto fake_restore;
else
reg += R_I0 - R_O0;
} else if (reg <= R_O7) {
return ((&rp->r_g1)[reg - 1]);
}
if (dtrace_getotherwin() > 0)
return (dtrace_getreg_win(reg, 1));
mpcb = (struct machpcb *)((caddr_t)rp - REGOFF);
if (curproc->p_model == DATAMODEL_NATIVE) {
struct frame *fr = (void *)(rp->r_sp + STACK_BIAS);
if (mpcb->mpcb_wbcnt > 0) {
struct rwindow *rwin = (void *)mpcb->mpcb_wbuf;
int i = mpcb->mpcb_wbcnt;
do {
i--;
if ((long)mpcb->mpcb_spbuf[i] == rp->r_sp)
return (rwin[i].rw_local[reg - 16]);
} while (i > 0);
}
DTRACE_CPUFLAG_SET(CPU_DTRACE_NOFAULT);
value = dtrace_fulword(&fr->fr_local[reg - 16]);
DTRACE_CPUFLAG_CLEAR(CPU_DTRACE_NOFAULT);
} else {
struct frame32 *fr = (void *)(uintptr_t)(caddr32_t)rp->r_sp;
if (mpcb->mpcb_wbcnt > 0) {
struct rwindow32 *rwin = (void *)mpcb->mpcb_wbuf;
int i = mpcb->mpcb_wbcnt;
do {
i--;
if ((long)mpcb->mpcb_spbuf[i] == rp->r_sp)
return (rwin[i].rw_local[reg - 16]);
} while (i > 0);
}
DTRACE_CPUFLAG_SET(CPU_DTRACE_NOFAULT);
value = dtrace_fuword32(&fr->fr_local[reg - 16]);
DTRACE_CPUFLAG_CLEAR(CPU_DTRACE_NOFAULT);
}
return (value);
fake_restore:
ASSERT(R_L0 <= reg && reg <= R_I7);
if (dtrace_getotherwin() > 1)
return (dtrace_getreg_win(reg, 2));
mpcb = (struct machpcb *)((caddr_t)rp - REGOFF);
if (dtrace_getotherwin() > 0) {
fp = dtrace_getreg_win(R_FP, 1);
goto got_fp;
}
if (curproc->p_model == DATAMODEL_NATIVE) {
struct frame *fr = (void *)(rp->r_sp + STACK_BIAS);
if (mpcb->mpcb_wbcnt > 0) {
struct rwindow *rwin = (void *)mpcb->mpcb_wbuf;
int i = mpcb->mpcb_wbcnt;
do {
i--;
if ((long)mpcb->mpcb_spbuf[i] == rp->r_sp) {
fp = rwin[i].rw_fp;
goto got_fp;
}
} while (i > 0);
}
DTRACE_CPUFLAG_SET(CPU_DTRACE_NOFAULT);
fp = dtrace_fulword(&fr->fr_savfp);
DTRACE_CPUFLAG_CLEAR(CPU_DTRACE_NOFAULT);
if (cpu_core[CPU->cpu_id].cpuc_dtrace_flags & CPU_DTRACE_FAULT)
return (0);
} else {
struct frame32 *fr = (void *)(uintptr_t)(caddr32_t)rp->r_sp;
if (mpcb->mpcb_wbcnt > 0) {
struct rwindow32 *rwin = (void *)mpcb->mpcb_wbuf;
int i = mpcb->mpcb_wbcnt;
do {
i--;
if ((long)mpcb->mpcb_spbuf[i] == rp->r_sp) {
fp = rwin[i].rw_fp;
goto got_fp;
}
} while (i > 0);
}
DTRACE_CPUFLAG_SET(CPU_DTRACE_NOFAULT);
fp = dtrace_fuword32(&fr->fr_savfp);
DTRACE_CPUFLAG_CLEAR(CPU_DTRACE_NOFAULT);
if (cpu_core[CPU->cpu_id].cpuc_dtrace_flags & CPU_DTRACE_FAULT)
return (0);
}
got_fp:
if (curproc->p_model == DATAMODEL_NATIVE) {
struct frame *fr = (void *)(fp + STACK_BIAS);
if (mpcb->mpcb_wbcnt > 0) {
struct rwindow *rwin = (void *)mpcb->mpcb_wbuf;
int i = mpcb->mpcb_wbcnt;
do {
i--;
if ((long)mpcb->mpcb_spbuf[i] == fp)
return (rwin[i].rw_local[reg - 16]);
} while (i > 0);
}
DTRACE_CPUFLAG_SET(CPU_DTRACE_NOFAULT);
value = dtrace_fulword(&fr->fr_local[reg - 16]);
DTRACE_CPUFLAG_CLEAR(CPU_DTRACE_NOFAULT);
} else {
struct frame32 *fr = (void *)(uintptr_t)(caddr32_t)fp;
if (mpcb->mpcb_wbcnt > 0) {
struct rwindow32 *rwin = (void *)mpcb->mpcb_wbuf;
int i = mpcb->mpcb_wbcnt;
do {
i--;
if ((long)mpcb->mpcb_spbuf[i] == fp)
return (rwin[i].rw_local[reg - 16]);
} while (i > 0);
}
DTRACE_CPUFLAG_SET(CPU_DTRACE_NOFAULT);
value = dtrace_fuword32(&fr->fr_local[reg - 16]);
DTRACE_CPUFLAG_CLEAR(CPU_DTRACE_NOFAULT);
}
return (value);
}
void
dtrace_setreg(struct regs *rp, uint_t reg, ulong_t val)
{
DTRACE_CPUFLAG_SET(CPU_DTRACE_ILLOP);
}
uint64_t
dtrace_getvmreg(uint_t ndx, volatile uint16_t *flags)
{
*flags |= CPU_DTRACE_ILLOP;
return (0);
}