#include <sys/cdefs.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/dtrace_impl.h>
#include <sys/kernel.h>
#include <sys/msan.h>
#include <sys/stack.h>
#include <sys/pcpu.h>
#include <cddl/dev/dtrace/dtrace_cddl.h>
#include <machine/frame.h>
#include <machine/md_var.h>
#include <machine/stack.h>
#include <x86/ifunc.h>
#include <vm/vm.h>
#include <vm/vm_param.h>
#include <vm/pmap.h>
#include "regset.h"
uint8_t dtrace_fuword8_nocheck(void *);
uint16_t dtrace_fuword16_nocheck(void *);
uint32_t dtrace_fuword32_nocheck(void *);
uint64_t dtrace_fuword64_nocheck(void *);
int dtrace_ustackdepth_max = 2048;
void
dtrace_getpcstack(pc_t *pcstack, int pcstack_limit, int aframes,
uint32_t *intrpc)
{
struct thread *td;
int depth = 0;
register_t rbp;
struct amd64_frame *frame;
vm_offset_t callpc;
pc_t caller = (pc_t) solaris_cpu[curcpu].cpu_dtrace_caller;
if (intrpc != 0)
pcstack[depth++] = (pc_t) intrpc;
aframes++;
__asm __volatile("movq %%rbp,%0" : "=r" (rbp));
frame = (struct amd64_frame *)rbp;
td = curthread;
while (depth < pcstack_limit) {
kmsan_mark(frame, sizeof(*frame), KMSAN_STATE_INITED);
if (!kstack_contains(curthread, (vm_offset_t)frame,
sizeof(*frame)))
break;
callpc = frame->f_retaddr;
if (!INKERNEL(callpc))
break;
if (aframes > 0) {
aframes--;
if ((aframes == 0) && (caller != 0)) {
pcstack[depth++] = caller;
}
} else {
pcstack[depth++] = callpc;
}
if ((vm_offset_t)frame->f_frame <= (vm_offset_t)frame)
break;
frame = frame->f_frame;
}
for (; depth < pcstack_limit; depth++) {
pcstack[depth] = 0;
}
kmsan_check(pcstack, pcstack_limit * sizeof(*pcstack), "dtrace");
}
static int
dtrace_getustack_common(uint64_t *pcstack, int pcstack_limit, uintptr_t pc,
uintptr_t sp)
{
uintptr_t oldsp;
volatile uint16_t *flags =
(volatile uint16_t *)&cpu_core[curcpu].cpuc_dtrace_flags;
int ret = 0;
ASSERT(pcstack == NULL || pcstack_limit > 0);
ASSERT(dtrace_ustackdepth_max > 0);
while (pc != 0) {
if (ret++ >= dtrace_ustackdepth_max) {
*flags |= CPU_DTRACE_BADSTACK;
cpu_core[curcpu].cpuc_dtrace_illval = sp;
break;
}
if (pcstack != NULL) {
*pcstack++ = (uint64_t)pc;
pcstack_limit--;
if (pcstack_limit <= 0)
break;
}
if (sp == 0)
break;
oldsp = sp;
pc = dtrace_fuword64((void *)(sp +
offsetof(struct amd64_frame, f_retaddr)));
sp = dtrace_fuword64((void *)sp);
if (sp == oldsp) {
*flags |= CPU_DTRACE_BADSTACK;
cpu_core[curcpu].cpuc_dtrace_illval = sp;
break;
}
if (*flags & CPU_DTRACE_FAULT) {
*flags &= ~CPU_DTRACE_FAULT;
break;
}
}
return (ret);
}
void
dtrace_getupcstack(uint64_t *pcstack, int pcstack_limit)
{
proc_t *p = curproc;
struct trapframe *tf;
uintptr_t pc, sp, fp;
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->tf_rip;
fp = tf->tf_rbp;
sp = tf->tf_rsp;
if (DTRACE_CPUFLAG_ISSET(CPU_DTRACE_ENTRY)) {
*pcstack++ = (uint64_t)pc;
pcstack_limit--;
if (pcstack_limit <= 0)
return;
pc = dtrace_fuword64((void *) sp);
}
n = dtrace_getustack_common(pcstack, pcstack_limit, pc, fp);
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, fp, 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->tf_rip;
fp = tf->tf_rbp;
sp = tf->tf_rsp;
if (DTRACE_CPUFLAG_ISSET(CPU_DTRACE_ENTRY)) {
pc = dtrace_fuword64((void *) sp);
n++;
}
n += dtrace_getustack_common(NULL, 0, pc, fp);
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, fp;
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->tf_rip;
sp = tf->tf_rsp;
fp = tf->tf_rbp;
#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;
pc = dtrace_fuword64((void *)sp);
}
while (pc != 0) {
*pcstack++ = (uint64_t)pc;
*fpstack++ = fp;
pcstack_limit--;
if (pcstack_limit <= 0)
break;
if (fp == 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
{
pc = dtrace_fuword64((void *)(fp +
offsetof(struct amd64_frame, f_retaddr)));
fp = dtrace_fuword64((void *)fp);
}
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)
{
struct thread *td;
uintptr_t val;
struct amd64_frame *fp = (struct amd64_frame *)dtrace_getfp();
uintptr_t *stack;
int i;
int inreg = 5;
td = curthread;
if (td->t_dtrace_trapframe != NULL) {
struct trapframe *tf = td->t_dtrace_trapframe;
if (arg <= inreg) {
switch (arg) {
case 0:
return (tf->tf_rdi);
case 1:
return (tf->tf_rsi);
case 2:
return (tf->tf_rdx);
case 3:
return (tf->tf_rcx);
case 4:
return (tf->tf_r8);
case 5:
return (tf->tf_r9);
}
}
arg -= inreg;
stack = (uintptr_t *)tf->tf_rsp;
goto load;
}
for (i = 1; i <= aframes; i++) {
kmsan_mark(fp, sizeof(*fp), KMSAN_STATE_INITED);
fp = fp->f_frame;
}
arg++;
if (arg <= inreg) {
DTRACE_CPUFLAG_SET(CPU_DTRACE_ILLOP);
return (0);
}
arg -= (inreg + 1);
stack = (uintptr_t *)&fp[1];
load:
DTRACE_CPUFLAG_SET(CPU_DTRACE_NOFAULT);
val = stack[arg];
DTRACE_CPUFLAG_CLEAR(CPU_DTRACE_NOFAULT);
kmsan_mark(&val, sizeof(val), KMSAN_STATE_INITED);
return (val);
}
int
dtrace_getstackdepth(int aframes)
{
int depth = 0;
struct amd64_frame *frame;
vm_offset_t rbp;
aframes++;
rbp = dtrace_getfp();
frame = (struct amd64_frame *)rbp;
depth++;
for (;;) {
kmsan_mark(frame, sizeof(*frame), KMSAN_STATE_INITED);
if (!kstack_contains(curthread, (vm_offset_t)frame,
sizeof(*frame)))
break;
depth++;
if (frame->f_frame <= frame)
break;
frame = frame->f_frame;
}
if (depth < aframes)
return 0;
else
return depth - aframes;
}
ulong_t
dtrace_getreg(struct trapframe *frame, uint_t reg)
{
int regmap[] = {
REG_GS,
REG_FS,
REG_ES,
REG_DS,
REG_RDI,
REG_RSI,
REG_RBP,
REG_RSP,
REG_RBX,
REG_RDX,
REG_RCX,
REG_RAX,
REG_TRAPNO,
REG_ERR,
REG_RIP,
REG_CS,
REG_RFL,
REG_RSP,
REG_SS
};
if (reg <= GS) {
if (reg >= sizeof (regmap) / sizeof (int)) {
DTRACE_CPUFLAG_SET(CPU_DTRACE_ILLOP);
return (0);
}
reg = regmap[reg];
} else {
reg -= GS + 1;
}
switch (reg) {
case REG_RDI:
return (frame->tf_rdi);
case REG_RSI:
return (frame->tf_rsi);
case REG_RDX:
return (frame->tf_rdx);
case REG_RCX:
return (frame->tf_rcx);
case REG_R8:
return (frame->tf_r8);
case REG_R9:
return (frame->tf_r9);
case REG_RAX:
return (frame->tf_rax);
case REG_RBX:
return (frame->tf_rbx);
case REG_RBP:
return (frame->tf_rbp);
case REG_R10:
return (frame->tf_r10);
case REG_R11:
return (frame->tf_r11);
case REG_R12:
return (frame->tf_r12);
case REG_R13:
return (frame->tf_r13);
case REG_R14:
return (frame->tf_r14);
case REG_R15:
return (frame->tf_r15);
case REG_DS:
return (frame->tf_ds);
case REG_ES:
return (frame->tf_es);
case REG_FS:
return (frame->tf_fs);
case REG_GS:
return (frame->tf_gs);
case REG_TRAPNO:
return (frame->tf_trapno);
case REG_ERR:
return (frame->tf_err);
case REG_RIP:
return (frame->tf_rip);
case REG_CS:
return (frame->tf_cs);
case REG_SS:
return (frame->tf_ss);
case REG_RFL:
return (frame->tf_rflags);
case REG_RSP:
return (frame->tf_rsp);
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)) {
dtrace_copy(uaddr, kaddr, size);
kmsan_mark((void *)kaddr, size, KMSAN_STATE_INITED);
}
}
void
dtrace_copyout(uintptr_t kaddr, uintptr_t uaddr, size_t size,
volatile uint16_t *flags)
{
if (dtrace_copycheck(uaddr, kaddr, size)) {
kmsan_check((void *)kaddr, size, "dtrace_copyout");
dtrace_copy(kaddr, uaddr, size);
}
}
void
dtrace_copyinstr(uintptr_t uaddr, uintptr_t kaddr, size_t size,
volatile uint16_t *flags)
{
if (dtrace_copycheck(uaddr, kaddr, size)) {
dtrace_copystr(uaddr, kaddr, size, flags);
kmsan_mark((void *)kaddr, size, KMSAN_STATE_INITED);
}
}
void
dtrace_copyoutstr(uintptr_t kaddr, uintptr_t uaddr, size_t size,
volatile uint16_t *flags)
{
if (dtrace_copycheck(uaddr, kaddr, size)) {
kmsan_check((void *)kaddr, size, "dtrace_copyoutstr");
dtrace_copystr(kaddr, uaddr, size, flags);
}
}
uint8_t
dtrace_fuword8(void *uaddr)
{
uint8_t val;
if ((uintptr_t)uaddr > VM_MAXUSER_ADDRESS) {
DTRACE_CPUFLAG_SET(CPU_DTRACE_BADADDR);
cpu_core[curcpu].cpuc_dtrace_illval = (uintptr_t)uaddr;
return (0);
}
val = dtrace_fuword8_nocheck(uaddr);
kmsan_mark(&val, sizeof(val), KMSAN_STATE_INITED);
return (val);
}
uint16_t
dtrace_fuword16(void *uaddr)
{
uint16_t val;
if ((uintptr_t)uaddr > VM_MAXUSER_ADDRESS) {
DTRACE_CPUFLAG_SET(CPU_DTRACE_BADADDR);
cpu_core[curcpu].cpuc_dtrace_illval = (uintptr_t)uaddr;
return (0);
}
val = dtrace_fuword16_nocheck(uaddr);
kmsan_mark(&val, sizeof(val), KMSAN_STATE_INITED);
return (val);
}
uint32_t
dtrace_fuword32(void *uaddr)
{
uint32_t val;
if ((uintptr_t)uaddr > VM_MAXUSER_ADDRESS) {
DTRACE_CPUFLAG_SET(CPU_DTRACE_BADADDR);
cpu_core[curcpu].cpuc_dtrace_illval = (uintptr_t)uaddr;
return (0);
}
val = dtrace_fuword32_nocheck(uaddr);
kmsan_mark(&val, sizeof(val), KMSAN_STATE_INITED);
return (val);
}
uint64_t
dtrace_fuword64(void *uaddr)
{
uint64_t val;
if ((uintptr_t)uaddr > VM_MAXUSER_ADDRESS) {
DTRACE_CPUFLAG_SET(CPU_DTRACE_BADADDR);
cpu_core[curcpu].cpuc_dtrace_illval = (uintptr_t)uaddr;
return (0);
}
val = dtrace_fuword64_nocheck(uaddr);
kmsan_mark(&val, sizeof(val), KMSAN_STATE_INITED);
return (val);
}
void dtrace_copy_nosmap(uintptr_t, uintptr_t, size_t);
void dtrace_copy_smap(uintptr_t, uintptr_t, size_t);
DEFINE_IFUNC(, void, dtrace_copy, (uintptr_t, uintptr_t, size_t))
{
return ((cpu_stdext_feature & CPUID_STDEXT_SMAP) != 0 ?
dtrace_copy_smap : dtrace_copy_nosmap);
}
void dtrace_copystr_nosmap(uintptr_t, uintptr_t, size_t, volatile uint16_t *);
void dtrace_copystr_smap(uintptr_t, uintptr_t, size_t, volatile uint16_t *);
DEFINE_IFUNC(, void, dtrace_copystr, (uintptr_t, uintptr_t, size_t,
volatile uint16_t *))
{
return ((cpu_stdext_feature & CPUID_STDEXT_SMAP) != 0 ?
dtrace_copystr_smap : dtrace_copystr_nosmap);
}
uintptr_t dtrace_fulword_nosmap(void *);
uintptr_t dtrace_fulword_smap(void *);
DEFINE_IFUNC(, uintptr_t, dtrace_fulword, (void *))
{
return ((cpu_stdext_feature & CPUID_STDEXT_SMAP) != 0 ?
dtrace_fulword_smap : dtrace_fulword_nosmap);
}
uint8_t dtrace_fuword8_nocheck_nosmap(void *);
uint8_t dtrace_fuword8_nocheck_smap(void *);
DEFINE_IFUNC(, uint8_t, dtrace_fuword8_nocheck, (void *))
{
return ((cpu_stdext_feature & CPUID_STDEXT_SMAP) != 0 ?
dtrace_fuword8_nocheck_smap : dtrace_fuword8_nocheck_nosmap);
}
uint16_t dtrace_fuword16_nocheck_nosmap(void *);
uint16_t dtrace_fuword16_nocheck_smap(void *);
DEFINE_IFUNC(, uint16_t, dtrace_fuword16_nocheck, (void *))
{
return ((cpu_stdext_feature & CPUID_STDEXT_SMAP) != 0 ?
dtrace_fuword16_nocheck_smap : dtrace_fuword16_nocheck_nosmap);
}
uint32_t dtrace_fuword32_nocheck_nosmap(void *);
uint32_t dtrace_fuword32_nocheck_smap(void *);
DEFINE_IFUNC(, uint32_t, dtrace_fuword32_nocheck, (void *))
{
return ((cpu_stdext_feature & CPUID_STDEXT_SMAP) != 0 ?
dtrace_fuword32_nocheck_smap : dtrace_fuword32_nocheck_nosmap);
}
uint64_t dtrace_fuword64_nocheck_nosmap(void *);
uint64_t dtrace_fuword64_nocheck_smap(void *);
DEFINE_IFUNC(, uint64_t, dtrace_fuword64_nocheck, (void *))
{
return ((cpu_stdext_feature & CPUID_STDEXT_SMAP) != 0 ?
dtrace_fuword64_nocheck_smap : dtrace_fuword64_nocheck_nosmap);
}