/*
**********************************************************************
*
* Component: RDB servers
* Module: servbkpt.c
*
* Synopsis: Management of breakpoints
*
* $Id$
*
**********************************************************************
*/
#include <sys/errno.h>
#include <assert.h>
#include <rdbg/rdbg.h>
#include <rdbg/servrpc.h>
/*----- Macros -----*/
#define BKPT0(plst) ((BASE_BREAK*)(plst)->break_list)
#define BKPT_INCR 5 /* how many bkpt slots alloc at a time */
#define BKPT_OVER(plst,idx,addr,size) \
((plst)->break_list[idx].ee_loc + BREAK_SIZE > (UINT32) (addr) \
&& (plst)->break_list[idx].ee_loc < (UINT32) (addr) + (size))
#define BKPT_SLOTS \
(sizeof ((xdr_break*) 0)->thread_list / \
sizeof ((xdr_break*) 0)->thread_list [0])
/*
* BreakAlloc - alloc a breakpoint entry.
*
* This is a generic routine to insert an entry in the
* breakpoint list. It returns the number of entry just
* created. It returns -1 if failed.
*/
static int
BreakAlloc (PID_LIST * plst, Boolean normal)
{
int idx, len;
xdr_break *blst;
if (!normal) { /* want 0 entry */
if (plst->break_list) {
return (0); /* already there */
}
idx = 1; /* force alloc below */
} else {
for (idx = 1; idx < (int) plst->break_alloc; idx++) {
if (plst->break_list[idx].type == BRKT_NONE) {
/*
* got a free one
*/
memset (&plst->break_list[idx], 0, sizeof (xdr_break));
return (idx); /* found one */
}
}
}
/*
* idx is the requested entry
*/
if (idx >= (int) plst->break_alloc) { /* need more space */
len = plst->break_alloc + BKPT_INCR;
blst = (xdr_break *) Realloc (plst->break_list, len * sizeof (xdr_break));
if (!blst) {
return (-1); /* failed, no space */
}
plst->break_alloc = len; /* got more */
plst->break_list = blst;
/*
* Clear new space
*/
memset (blst + len - BKPT_INCR, 0, BKPT_INCR * sizeof (xdr_break));
idx = len - BKPT_INCR; /* next available */
if (!idx) {
idx = 1; /* for normal cases */
}
}
return (normal ? idx : 0); /* return it */
}
/*
* BreakSet - set a breakpoint in process
*
* Returns the number or -1/errno.
*/
#ifdef DDEBUG
static const char *BreakTypes[] = {
"NONE", "INSTR", "READ", "WRITE",
"ACCESS", "EXEC", "OS_CALL", "OS_SWITCH",
"STEPEMUL"
};
#define BN_MAX (sizeof BreakTypes / sizeof BreakTypes[0])
#define BREAK_NAME(t) ((unsigned) (t) < BN_MAX ? BreakTypes[t] : "???")
#endif
int
BreakSet (PID_LIST * plst, int conn_idx, xdr_break * bkpt)
{
int pid = plst->pid;
int type = bkpt->type;
void *addr = (void *) bkpt->ee_loc;
int idx;
int data;
DPRINTF (("BreakSet: type %d (%s) at 0x%x th %d ee_type %d len %d "
"pass %d curr %d list %d %d %d %d\n", type, BREAK_NAME (type),
(int) addr,
bkpt->thread_spec, bkpt->ee_type, bkpt->length, bkpt->pass_count,
bkpt->curr_pass, bkpt->thread_list[0], bkpt->thread_list[1],
bkpt->thread_list[2], bkpt->thread_list[3]));
idx = BreakAlloc (plst, True); /* get entry */
if (idx < 0) { /* no memory */
setErrno (ENOMEM); /* set for safety */
return -1; /* return the error */
}
data = TgtPtrace (RPT_PEEKTEXT, pid, addr, 0, NULL); /* current */
if (getErrno ()) {
return -1; /* failed, return the error */
}
if (IS_BREAK (data)) { /* There is already a break here */
DPRINTF (("BreakSet: already have soft bkpt at %x\n", addr));
if (type == BRKT_STEPEMUL) {
++BKPT0 (plst)->pad1;
return 1; /* Any non-error value is OK */
}
setErrno (EBUSY);
return -1;
}
TgtPtrace (RPT_POKETEXT, pid, addr, SET_BREAK (data), NULL);
if (getErrno ()) {
return -1;
}
plst->break_list[idx] = *bkpt;
plst->break_list[idx].ee_type = data; /* saved data */
/*
* Inform other owners
*/
if (type != BRKT_STEPEMUL) {
TgtNotifyAll (plst - pid_list, BMSG_BREAK, 1 /*added */ , idx,
conn_idx, False);
} else {
++BKPT0 (plst)->pad1;
}
/*
* Return the number
*/
setErrno (0); /* Just in case */
return idx;
}
int
BreakSetAt (PID_LIST * plst, int conn_idx, unsigned long addr,
break_type type)
{
xdr_break xb;
memset (&xb, 0, sizeof xb);
xb.type = type;
xb.ee_loc = addr;
return BreakSet (plst, conn_idx, &xb);
}
/*----- Find a breakpoint by address -----*/
int
BreakGetIndex (PID_LIST * plst, void *addr)
{
int idx;
int data = -1;
if (!plst->break_alloc) {
setErrno (EFAULT);
return -1;
}
for (idx = 1; idx < (int) plst->break_alloc; idx++) {
if ((u_long) addr == plst->break_list[idx].ee_loc) {
data = idx;
break;
}
}
return data;
}
/*----- Getting information about breakpoint -----*/
/*
* If data > 0, fill "bkpt" with information about breakpoint
* and return the number of the next one.
* If data == 0, return the count of breakpoints.
*/
int
BreakGet (const PID_LIST * plst, int data, xdr_break * bkpt)
{
int idx;
if (!data) { /* just count them */
for (idx = 1; idx < (int) plst->break_alloc; idx++) {
if (plst->break_list[idx].type != BRKT_NONE) {
data++;
}
}
return data; /* count */
}
if ((unsigned) data >= plst->break_alloc) {
/*
* out of range
*/
setErrno (EFAULT); /* closest match */
return -1;
}
/*
* get it and say which is next
*/
*bkpt = plst->break_list[data];
for (idx = (int) data + 1; idx < (int) plst->break_alloc; idx++) {
if (plst->break_list[idx].type != BRKT_NONE) {
return idx;
}
}
return 0; /* otherwise returns 0 for no more */
}
/*----- Clearing bkpts -----*/
/*
* BreakClear - clear one (if data != 0) or all breakpoints
* (if data == 0). Return the number of bkpts cleared.
* If (data == -1), remove step-emulation breakpoints.
*/
int
BreakClear (PID_LIST * plst, int conn_idx, int data)
{
int pid_idx = plst - pid_list;
int idx;
int cleared = 0;
int clearStepEmul = 0;
int terminated = PROC_TERMINATED (plst);
int stepEmulCount = 0;
/*
* break handle in data
*/
if (!plst->break_alloc) { /* there are no breaks */
DPRINTF (("BreakClear: no bkpts defined.\n"));
setErrno (EFAULT); /* closest match */
return -1; /* return error */
}
if (!data) { /* clear all */
idx = 1;
data = plst->break_alloc - 1;
/*
* Inform other owners
*/
DPRINTF (("BreakClear: clearing all bkpts.\n"));
TgtNotifyAll (pid_idx, BMSG_BREAK, 0 /*clr */ , 0, conn_idx, False);
} else if (data == -1) { /* clear all step-emul bkpts */
DPRINTF (("BreakClear: removing %d step-emul bkpts\n",
BKPT0 (plst)->pad1));
stepEmulCount = BKPT0 (plst)->pad1;
BKPT0 (plst)->pad1 = 0;
clearStepEmul = 1;
idx = 1;
data = plst->break_alloc - 1;
} else if ((unsigned) data >= plst->break_alloc
|| plst->break_list[data].type == BRKT_NONE) {
/*
* out of range
*/
DPRINTF (("BreakClear: invalid bkpt %d\n", data));
setErrno (EFAULT); /* closest match */
return -1; /* return error */
} else {
idx = data;
/*
* Inform other owners
*/
TgtNotifyAll (pid_idx, BMSG_BREAK, 0 /*clr */ , idx, conn_idx, False);
DPRINTF (("BreakClear: clearing bkpt %d\n", data));
}
for (; idx <= data; idx++) { /* clear each one */
int type = plst->break_list[idx].type;
if (clearStepEmul && type != BRKT_STEPEMUL)
continue;
if (type == BRKT_INSTR || (clearStepEmul && type == BRKT_STEPEMUL)) {
/*
* just patch back
*/
char *addr = (char *) plst->break_list[idx].ee_loc;
int val;
if (BKPT0 (plst)->clr_step && BKPT0 (plst)->last_break == idx) {
BKPT0 (plst)->clr_step = 0; /* not needed */
}
/*
* Neighboring bytes can have breakpoints too...
*/
if (!terminated) {
setErrno (0);
val = TgtPtrace (RPT_PEEKTEXT, plst->pid, addr, 0, NULL);
if (getErrno ()) {
DPRINTF (("BreakClear: addr %x not readable!\n", addr));
setErrno (0); /* Forget bkpt */
} else {
assert (IS_BREAK (val));
val = ORG_BREAK (val, (int) plst->break_list[idx].ee_type);
TgtPtrace (RPT_POKETEXT, plst->pid, addr, val, NULL);
if (getErrno ()) {
DPRINTF (("BreakClear: addr %x not writable!\n", addr));
setErrno (0);
}
}
}
++cleared; /* indicate cleared */
}
memset (&plst->break_list[idx], 0, sizeof (xdr_break));
}
assert (!clearStepEmul || cleared <= stepEmulCount);
if (stepEmulCount && cleared == 0) {
DPRINTF (("BreakClear: all STEPEMUL bkpts were shared\n"));
return 1;
}
return cleared;
}
/*----- Hiding of breakpoints -----*/
/*
* PatchBreak - patch original data from break into data buffer.
*
* Notes:
* - this routine patches the original data under a break into the data
* buffer from a ptrace read/peek.
*/
static void
PatchBreak (char *buff, UINT32 bstart, int bsize, UINT32 dstart, char *dvalue)
{
int size = BREAK_SIZE; /* default size */
/*
* Must deal with all sorts of unalignments
* * (3 full overlaps, 3 unaligns)
*/
if (bsize < BREAK_SIZE) {
/*
* case where buffer is smaller than data
*/
memcpy (buff, dvalue + (bstart - dstart), bsize); /* copy over */
return;
}
/*
* buffer larger than data.
* * we need to see where break fits in buffer and whether
* * we have part of it off the end. We set bstart to be the
* * buffer offset, dtart to be the break data offset, and
* * size to be the amount to copy
*/
if (dstart < bstart) {
/*
* break before actual buffer
*/
dstart = bstart - dstart; /* offset in data */
size -= dstart; /* amount to copy */
bstart = 0; /* offset in buffer */
} else if (dstart + size > bstart + bsize) {
/*
* off end
*/
bstart += bsize; /* end of buffer */
size -= (dstart + size) - bstart;
bstart = bsize - size; /* come back into buffer enough */
dstart = 0; /* start of data */
} else { /* normal case */
bstart = dstart - bstart; /* offset in buffer */
dstart = 0;
}
memcpy (buff + bstart, dvalue + dstart, size);
}
void
BreakHide (const PID_LIST * plst, void *addr, int data, void *addr2)
{
int idx;
if (!plst->break_list) /* no breaks exist, so skip this */
return;
/*
* if breakpoints, replace
*/
for (idx = 1; idx < (int) plst->break_alloc; idx++) {
int type = plst->break_list[idx].type;
if (type != BRKT_INSTR && type != BRKT_STEPEMUL) {
continue;
}
/*
* break, see if overlaps
*/
if (BKPT_OVER (plst, idx, addr, data)) {
/*
* overlaps, patch in old value
*/
PatchBreak ((char *) addr2, (UINT32) addr, data,
plst->break_list[idx].ee_loc,
(char *) &plst->break_list[idx].ee_type);
}
}
}
/*----- Checking of breakpoint overwrites -----*/
/*
* BreakOverwrite - check if memory write does not involve addresses
* having software breakpoints.
*/
int
BreakOverwrite (const PID_LIST * plst, const char *addr, unsigned int size)
{
int idx;
if (!plst->break_list) { /* No breaks exist */
return 0;
}
for (idx = 1; idx < (int) plst->break_alloc; idx++) {
int type = plst->break_list[idx].type;
/*
* Consider only breakpoints involving modified memory
*/
if (type != BRKT_INSTR && type != BRKT_STEPEMUL) {
continue;
}
if (BKPT_OVER (plst, idx, addr, size)) {
return -1; /* overlaps */
}
}
return 0;
}
/*----- Execution support -----*/
/*
* BreakStepRange - Start stepping in a range.
*
* Range is saved in breakpoint 0.
*/
int
BreakStepRange (PID_LIST * plst, void *addr, int len)
{
if (!plst->break_list) {
/*
* get list
*/
if (BreakAlloc (plst, False) == -1) { /* must not be any memory */
setErrno (ENOMEM); /* to be safe */
return -1; /* fails */
}
}
BKPT0 (plst)->range_start = (UINT32) addr;
BKPT0 (plst)->range_end = (UINT32) addr + (len - 1);
return 0;
}
/*
* If the Program Counter is changed, consider that the
* current breakpoint has not been reached yet.
*/
void
BreakPcChanged (PID_LIST * plst)
{
if (plst->break_list) {
/*
* clear break stuff
*/
BKPT0 (plst)->clr_step = False;
}
}
/*
* BreakStepOff - prepare stepping off a breakpoint.
*/
int
BreakStepOff (const PID_LIST * plst, void **paddr2)
{
if (plst->break_list && BKPT0 (plst)->clr_step) {
/*
* need clear then step off break
*/
int last = BKPT0 (plst)->last_break;
/*
* clear break, step, then do exec
*/
*paddr2 = (void *) plst->break_list[last].ee_type;
/*
* Need to clr_step after TgtPtrace() when wait() returns
*/
return 1;
}
return 0;
}
/*
* BreakSteppedOff - check if just stepped off a breakpoint
* and re-insert it into the code.
*/
void
BreakSteppedOff (PID_LIST * plst)
{
if (plst->break_list && BKPT0 (plst)->clr_step) {
int idx = BKPT0 (plst)->last_break;
int data;
BKPT0 (plst)->clr_step = 0;
/*
* Re-insert the breakpoint.
*/
data = TgtPtrace (RPT_PEEKTEXT, plst->pid,
(char *) plst->break_list[idx].ee_loc, 0, NULL);
assert (!IS_BREAK (data));
TgtPtrace (RPT_POKETEXT, plst->pid,
(char *) plst->break_list[idx].ee_loc,
(int) SET_BREAK (data), NULL);
}
}
/*
* Returns whether a thread matches a breakpoint.
*/
static int
BreakThreadMatch (xdr_break * xb, int thread)
{
int slot;
if (thread < 0)
return 1; /* Break existence check only */
if (xb->thread_list[0] == 0)
return 1; /* Universal break */
for (slot = 0; slot < BKPT_SLOTS; ++slot) {
if (xb->thread_list[slot] == 0)
return 0; /* End of list */
if (xb->thread_list[slot] == thread)
return 1; /* Match */
}
return 0; /* No matches found */
}
int
BreakAdjustPC (PID_LIST * plst)
{
/*
* BREAK_ADJ is the value by which the Program Counter
* has to be decremented after a software breakpoint
* is hit. It must be defined and can be zero.
*/
#if BREAK_ADJ
/*
* subtract back if necessary
*/
plst->regs.REG_PC -= BREAK_ADJ; /* now write back */
TgtPtrace (RPT_SETREGS, plst->pid, (char *) &plst->regs, 0, NULL);
#else
(void) plst;
#endif
return 0;
}
/*
* Identify the current breakpoint. The process just stopped.
*/
int
BreakIdentify (PID_LIST * plst, int adjust, int thread)
{
int foreignBkpt = 0;
int bidx;
for (bidx = 1; bidx < (int) plst->break_alloc; bidx++) {
int type = plst->break_list[bidx].type;
if ((type == BRKT_INSTR || type == BRKT_STEPEMUL)
&& plst->regs.REG_PC - BREAK_ADJ == plst->break_list[bidx].ee_loc) { /* found matching */
if (!BreakThreadMatch (&plst->break_list[bidx], thread)) {
if (foreignBkpt == 0) {
foreignBkpt = bidx;
}
continue;
}
if (adjust) {
BreakAdjustPC (plst);
}
return bidx;
}
}
if (foreignBkpt) {
if (adjust) {
BreakAdjustPC (plst);
}
return -foreignBkpt;
}
return 0;
}