/* ********************************************************************** * * Component: RDB servers * Module: servbkpt.c * * Synopsis: Management of breakpoints * * $Id$ * ********************************************************************** */ #include #include #include #include /*----- 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; }