#include <sys/kdebug.h>
#include <sys/errno.h>
#include <sys/param.h>
#include <sys/proc.h>
#include <sys/vm.h>
#include <sys/sysctl.h>
#include <vm/vm_kern.h>
unsigned int pc_buftomem = 0;
u_long * pc_buffer = 0;
u_long * pc_bufptr = 0;
u_long * pc_buflast = 0;
unsigned int npcbufs = 8192;
unsigned int pc_bufsize = 0;
unsigned int pcsample_flags = 0;
unsigned int pcsample_enable = 0;
pid_t pc_sample_pid = 0;
boolean_t pc_trace_frameworks = FALSE;
char pcsample_comm[MAXCOMLEN + 1];
u_long pcsample_beg = 0;
u_long pcsample_end = 0;
static pid_t global_state_pid = -1;
extern int pc_trace_buf[];
extern int pc_trace_cnt;
int
enable_branch_tracing()
{
#ifndef i386
struct proc *p;
if (-1 != pc_sample_pid) {
p = pfind(pc_sample_pid);
if (p) {
p->p_flag |= P_BTRACE;
}
}
else {
pc_trace_frameworks = TRUE;
}
return 1;
#else
return 0;
#endif
}
int
disable_branch_tracing()
{
struct proc *p;
switch (pc_sample_pid) {
case -1:
pc_trace_frameworks = FALSE;
break;
case 0:
break;
default:
p = pfind(pc_sample_pid);
if (p) {
p->p_flag &= ~P_BTRACE;
}
break;
}
clr_be_bit();
return 1;
}
int
branch_tracing_enabled()
{
struct proc *p = current_proc();
if (TRUE == pc_trace_frameworks) return TRUE;
if (p) {
return (P_BTRACE == (p->p_flag & P_BTRACE));
}
return 0;
}
void
add_pcbuffer()
{
int i;
u_long pc;
struct proc *curproc;
extern unsigned int kdebug_flags;
if (!pcsample_enable)
return;
for (i=0; i < pc_trace_cnt; i++)
{
pc = pc_trace_buf[i];
if ((pcsample_beg <= pc) && (pc < pcsample_end))
{
if (pc_bufptr > pc_buffer)
{
if ( (*(pc_bufptr-1)) == pc )
continue;
}
*pc_bufptr = (u_long)pc;
pc_bufptr++;
}
}
if ((pc_bufptr + pc_trace_cnt) >= pc_buflast)
{
pcsample_enable = 0;
(void)disable_branch_tracing();
wakeup(&pcsample_enable);
}
return;
}
pcsamples_bootstrap()
{
if (!disable_branch_tracing())
return(ENOTSUP);
pc_bufsize = npcbufs * sizeof(* pc_buffer);
if (kmem_alloc(kernel_map, &pc_buftomem,
(vm_size_t)pc_bufsize) == KERN_SUCCESS)
pc_buffer = (u_long *) pc_buftomem;
else
pc_buffer= (u_long *) 0;
if (pc_buffer) {
pc_bufptr = pc_buffer;
pc_buflast = &pc_bufptr[npcbufs];
pcsample_enable = 0;
return(0);
} else {
pc_bufsize=0;
return(EINVAL);
}
}
pcsamples_reinit()
{
int x;
int ret=0;
pcsample_enable = 0;
if (pc_bufsize && pc_buffer)
kmem_free(kernel_map,pc_buffer,pc_bufsize);
ret= pcsamples_bootstrap();
return(ret);
}
pcsamples_clear()
{
global_state_pid = -1;
pcsample_enable = 0;
if(pc_bufsize && pc_buffer)
kmem_free(kernel_map,pc_buffer,pc_bufsize);
pc_buffer = (u_long *)0;
pc_bufptr = (u_long *)0;
pc_buflast = (u_long *)0;
pc_bufsize = 0;
pcsample_beg= 0;
pcsample_end= 0;
bzero((void *)pcsample_comm, sizeof(pcsample_comm));
(void)disable_branch_tracing();
pc_sample_pid = 0;
pc_trace_frameworks = FALSE;
}
pcsamples_control(name, namelen, where, sizep)
int *name;
u_int namelen;
char *where;
size_t *sizep;
{
int ret=0;
int size=*sizep;
unsigned int value = name[1];
pcinfo_t pc_bufinfo;
pid_t *pidcheck;
pid_t curpid;
struct proc *p, *curproc;
if (name[0] != PCSAMPLE_GETNUMBUF)
{
if(curproc = current_proc())
curpid = curproc->p_pid;
else
return (ESRCH);
if (global_state_pid == -1)
global_state_pid = curpid;
else if (global_state_pid != curpid)
{
if((p = pfind(global_state_pid)) == NULL)
{
global_state_pid = curpid;
}
else
{
return(EBUSY);
}
}
}
switch(name[0]) {
case PCSAMPLE_DISABLE:
pcsample_enable=0;
break;
case PCSAMPLE_SETNUMBUF:
if (value < pc_trace_cnt) {
ret=EINVAL;
break;
}
if (value <= MAX_PCSAMPLES)
npcbufs = value;
else
npcbufs = MAX_PCSAMPLES;
break;
case PCSAMPLE_GETNUMBUF:
if(size < sizeof(pcinfo_t)) {
ret=EINVAL;
break;
}
pc_bufinfo.npcbufs = npcbufs;
pc_bufinfo.bufsize = pc_bufsize;
pc_bufinfo.enable = pcsample_enable;
pc_bufinfo.pcsample_beg = pcsample_beg;
pc_bufinfo.pcsample_end = pcsample_end;
if(copyout (&pc_bufinfo, where, sizeof(pc_bufinfo)))
{
ret=EINVAL;
}
break;
case PCSAMPLE_SETUP:
ret=pcsamples_reinit();
break;
case PCSAMPLE_REMOVE:
pcsamples_clear();
break;
case PCSAMPLE_READBUF:
if (value == 0)
{
pcsample_enable = 0;
(void)disable_branch_tracing();
ret = pcsamples_read(where, sizep);
break;
}
else if ((pc_bufsize <= 0) || (!pc_buffer))
{
ret=EINVAL;
break;
}
if (!enable_branch_tracing())
{
ret = ENOTSUP;
break;
}
pcsample_enable = 1;
ret = tsleep(&pcsample_enable, PRIBIO | PCATCH, "pcsample", 0);
pcsample_enable = 0;
(void)disable_branch_tracing();
if (ret)
{
if (ret)
{
*sizep = 0;
}
}
else
{
ret = pcsamples_read(where, sizep);
}
break;
case PCSAMPLE_SETREG:
if (size < sizeof(pcinfo_t))
{
ret = EINVAL;
break;
}
if (copyin(where, &pc_bufinfo, sizeof(pcinfo_t)))
{
ret = EINVAL;
break;
}
pcsample_beg = pc_bufinfo.pcsample_beg;
pcsample_end = pc_bufinfo.pcsample_end;
break;
case PCSAMPLE_COMM:
if (!(sizeof(pcsample_comm) > size))
{
ret = EINVAL;
break;
}
bzero((void *)pcsample_comm, sizeof(pcsample_comm));
if (copyin(where, pcsample_comm, size))
{
ret = EINVAL;
break;
}
if (pcsample_comm[0] != '\0')
{
ret= EOPNOTSUPP;
break;
}
else
{
if (size != (2 * sizeof(pid_t)))
{
ret = EINVAL;
break;
}
else
{
pidcheck = (pid_t *)pcsample_comm;
pc_sample_pid = pidcheck[1];
}
}
break;
default:
ret= EOPNOTSUPP;
break;
}
return(ret);
}
pcsamples_read(u_long *buffer, size_t *number)
{
int count=0;
int ret=0;
int copycount;
count = (*number)/sizeof(u_long);
if (count && pc_bufsize && pc_buffer)
{
copycount = pc_bufptr - pc_buffer;
if (copycount <= 0)
{
*number = 0;
return(0);
}
if (copycount > count)
copycount = count;
if(copyout(pc_buffer, buffer, copycount * sizeof(u_long)))
{
*number = 0;
return(EINVAL);
}
*number = copycount;
pc_bufptr = pc_buffer;
return(0);
}
else
{
*number = 0;
return(0);
}
}