/* ************************************************************************** * * Component: RDB servers * Module: servtgt.c * * $Id$ * ************************************************************************** */ #include #include #include #include #include #include #ifdef DDEBUG #define Ptrace TgtDbgPtrace #else #define Ptrace TgtRealPtrace #endif /* ---------------------------------------------------------------- TgtBreakRestoreOrig - Restore original instruction at "addr" just before single-stepping it. ---------------------------------------------------------------- */ int TgtBreakRestoreOrig (int pid, void *addr, void *addr2) /* Process identifier */ /* Breakpoint address */ /* Original instruction or bkpt number */ { int ret; int l; l = (long)Ptrace(RPT_PEEKTEXT, pid, addr, 0, NULL); /* assume ok */ ret = ORG_BREAK (l, (UINT32) addr2); /* reconstruct old instr */ ret = Ptrace(RPT_POKETEXT, pid, addr, ret, NULL); /* poke back old */ return ret; } /* ----------------------------------------------------------------------- TgtBreakCancelStep - Restore the breakpoint at "addr" if the single-step has failed at the ptrace level. ----------------------------------------------------------------------- */ #define BKPT0(plst) ((BASE_BREAK*)(plst)->break_list) void TgtBreakCancelStep (PID_LIST* plst) { assert (plst->break_list); assert (BKPT0 (plst)->clr_step); if (plst->break_list && BKPT0 (plst)->clr_step) { int idx = BKPT0 (plst)->last_break; int data; data = Ptrace (RPT_PEEKTEXT, plst->pid, (char *)plst->break_list [idx].ee_loc, 0, NULL); assert (! IS_BREAK (data)); Ptrace (RPT_POKETEXT, plst->pid, (char *)plst->break_list[idx].ee_loc, (int) SET_BREAK (data), NULL); } } /* ----------------------------------------------------------------------- TgtCreateNew - add a new process into the process management lists. ----------------------------------------------------------------------- */ void TgtCreateNew(PID pid, int conn, INT32 child, char *name, Boolean spawn) { int idx; for (idx = 0; idx < pid_list_cnt; idx++) if (!pid_list[idx].pid) break; /* find empty */ if (idx >= pid_list_cnt) { /* no empties, add more */ PID_LIST *tmp_pid_list = pid_list; pid_list_cnt += PID_LIST_INC; pid_list = (PID_LIST*) Realloc(pid_list, /* get new or extend */ pid_list_cnt * sizeof(PID_LIST)); if (!pid_list) { /* out of memory */ pid_list_cnt -= PID_LIST_INC; if (pid_list_cnt) { /* realloc failed - malloc again */ pid_list = tmp_pid_list; /* above relies on old pointer being valid after failed realloc */ } return; /* failed */ } /* now clear newly added space */ memset(pid_list+pid_list_cnt-PID_LIST_INC, 0, PID_LIST_INC * sizeof(PID_LIST)); idx = pid_list_cnt - PID_LIST_INC; } else /* clear entry we found */ memset(&pid_list[idx], 0, sizeof(PID_LIST)); /* now fill in empty entry */ pid_list[idx].pid = pid; pid_list[idx].running = 1; /* we have not called wait yet */ pid_list[idx].primary_conn = (UCHAR)conn; /* primary owner */ if (conn != -1) { /* found caller */ pid_list[idx].owners = 1; PIDMAP_SET (conn, idx); /* mask in */ } pid_list[idx].thread = (UINT32)-1; /* no thread for now */ pid_list[idx].last_start = LAST_START; /* handle MiX bug */ pid_list[idx].name = name ? (char *)StrDup(name) : (char *)NULL; } /* ----------------------------------------------------------------------- TgtNotifyWaitChange - send event to clients indicating child changed state. ----------------------------------------------------------------------- */ void TgtNotifyWaitChange( PID pid, int status, Boolean exclude) { int conn, idx; idx = FindPidEntry (pid); /* locate the pid that changed */ if (idx < 0) { DPRINTF(("TgtNotifyWaitChange: pid %d not in our list\n", (int) pid)); return; /* not in our list */ } pid_list[idx].running = 0; /* not running */ pid_list[idx].state = status; /* save status of stop/term */ if (!pid_list[idx].owners && !STS_SIGNALLED(status)) TgtDelete(&pid_list[idx], -1, 0); /* terminated and no owners */ else { /* normal cases */ for (conn = 0; conn < conn_list_cnt; conn++) { /* now find all interested clients */ if (!conn_list[conn].in_use /* free entry */ || ! PIDMAP_TEST (conn, idx)) continue; /* not using this pid */ if (conn == exclude) continue; /* do not do this one */ TspSendWaitChange(conn, BMSG_WAIT, 1, pid, 0, False);/* notify of change */ } } } /* ----------------------------------------------------------------------- TgtNotifyAll - send a message to all clients interested in process. ----------------------------------------------------------------------- */ void TgtNotifyAll( int pid_idx, BACK_MSG msg, UINT16 spec, UINT32 context, int exclude, Boolean force) { int conn; DPRINTF(("TgtNotifyAll: msg %d (%s) for pid_idx=%d (%d,%d)\n", msg, BmsgNames [msg], pid_idx, exclude, force)); for (conn = 0; conn < conn_list_cnt; conn++) if (conn_list[conn].in_use /* not free */ && PIDMAP_TEST (conn, pid_idx)) { if (conn != exclude) TspSendWaitChange(conn, msg, spec, pid_list[pid_idx].pid, context, force); } } /* ----------------------------------------------------------------------- TgtDelete - mark process as now uncontrolled. Notes: - this function removes a process from the process list. - the notify argument indicates a message to send if needed. ----------------------------------------------------------------------- */ void TgtDelete(PID_LIST *plst, int conn_idx, BACK_MSG notify) { int idx = plst - pid_list, cnt, conn; /* found */ cnt = pid_list[idx].owners; if (cnt) { /* some connections to break */ for (conn = 0; cnt && conn < conn_list_cnt; conn++) if (conn_list[conn].in_use /* not free */ && PIDMAP_TEST (conn, idx)) { /* found one that uses it */ PIDMAP_CLEAR (conn, idx); if (notify && conn != conn_idx) TspSendWaitChange(conn, notify, 0, plst->pid, 0, True); if (!--cnt) break; } } if (pid_list[idx].name) Free(pid_list[idx].name); /* free string name back */ /* Free breakpoint list */ if (pid_list [idx].break_list != NULL) { Free (pid_list [idx].break_list); } pid_list[idx].pid = 0; /* gone */ } /* ----------------------------------------------------------------------- TgtKillAndDelete - kill or detach process and remove entry. ----------------------------------------------------------------------- */ int TgtKillAndDelete( PID_LIST *plst, struct svc_req *rqstp, Boolean term) { ptrace_in pin; /* used for ptrace call */ ptrace_out *pout; /* Remove breakpoints */ if (plst->break_alloc > 0) { pin.pid = plst->pid; pin.addr.req = RPT_CLRBREAK; pin.data = 0; /* clear all */ pin.flags = PTRFLG_NON_OWNER; pout = RPCGENSRVNAME(ptrace_2_svc) (&pin, rqstp); if (pout->result < 0) { DPRINTF (("TgtKillAndDelete: RPT_CLRBREAK failed %d\n", getErrno())); return -1; } } if (term) { /* kill */ pin.addr.ptrace_addr_data_in_u.address = 0; pin.data = -1; /* Don't want notification from slave */ pin.addr.req = RPT_KILL; } else { /* detach */ pin.addr.ptrace_addr_data_in_u.address = 1; pin.data = 0; pin.addr.req = RPT_DETACH; } pin.pid = plst->pid; pin.flags = PTRFLG_FREE | PTRFLG_NON_OWNER; DPRINTF (("TgtKillAndDelete: ptrace_2_svc (%s (%d), %d)\n", PtraceName (pin.addr.req), pin.addr.req, pin.pid)); pout = RPCGENSRVNAME(ptrace_2_svc) (&pin, rqstp);/* start it */ if (pout->errNo == ESRCH && plst->pid) TgtDelete(plst, -1, BMSG_KILLED); /* only entry remains */ return 0; } /* ----------------------------------------------------------------------- TgtDetachCon - detach a connection's ownership of a process. ----------------------------------------------------------------------- */ void TgtDetachCon( int conn_idx, int pid_idx, Boolean delete) { if ((unsigned) pid_idx >= pid_list_cnt || !pid_list[pid_idx].pid) return; /* not valid */ if (PIDMAP_TEST (conn_idx, pid_idx)) { /* if an owner, release control */ PIDMAP_CLEAR (conn_idx, pid_idx); if (pid_list[pid_idx].owners) pid_list[pid_idx].owners--; if (pid_list[pid_idx].primary_conn == conn_idx) pid_list[pid_idx].primary_conn = NO_PRIMARY; if (delete && !pid_list[pid_idx].owners && PROC_TERMINATED (pid_list + pid_idx)) TgtDelete(&pid_list[pid_idx], -1, 0); /* remove entry */ } } /* ----------------------------------------------------------------------- TgtHandleChildChange - decide what action to take after wait() returns. Used in the master only. ----------------------------------------------------------------------- */ #ifdef DDEBUG static char* LastStartNames[] = { "NONE", "STEP", "CONT", "RANGE", "STEPOFF", "KILLED", "DETACHED" }; char* GetLastStartName (int last_start) { static char buf [32]; strcpy (buf, LastStartNames [last_start & ~LAST_START]); if (last_start & LAST_START) { strcat (buf, "+START"); } return buf; } #endif Boolean TgtHandleChildChange(PID pid, int* status, int *unexp, CPU_Exception_frame* ctx) { /* return False if continue, else stop */ int idx, sig; int bidx = 0; PID_LIST *plst; unsigned long PC; BASE_BREAK *base = NULL; /* break_list[0] is really BASE_BREAK */ int hadStepEmul; int origHadStepEmul; int stopWanted; DPRINTF (("TgtHandleChildChange: pid %d status %x cap\n", (int) pid, *status)); if (unexp) *unexp = 0; /* initialize to ok */ /* first, find pid in question */ idx = FindPidEntry (pid); if (idx < 0) { /* cannot locate this process */ DPRINTF (("TgtHandleChildChange: unknown process (%s pid)\n", FindPidEntry (pid) >= 0 ? "stale" : "unknown")); if (unexp) *unexp = 1; /* Unexpected change */ return(False); /* unknown: ignore (used to stop and notify) */ } /* found */ plst = &pid_list[idx]; /* pointer to entry */ /* first we see if just stopped */ /* copy ctxt */ CtxToRegs(ctx, &(plst->regs)); stopWanted = plst->stop_wanted; plst->stop_wanted = 0; /* For the next time */ hadStepEmul = BreakClear (plst, -1, -1) > 0; origHadStepEmul = hadStepEmul; /* hadStepEmul is cleared if real bkpt met */ if (STS_SIGNALLED (*status)) { /* stopped, not terminated */ sig = STS_GETSIG (*status); /* signal that stopped us */ /* now, we read the registers and see what to do next */ if (TgtPtrace(RPT_GETREGS, pid, (void *)&plst->regs, 0, NULL) < 0) { memset (&plst->regs, 0, sizeof plst->regs); } /* Get current thread */ plst->thread = TgtPtrace(RPT_GETTARGETTHREAD, pid, NULL, 0, NULL); if (sig == SIGTRAP) { /* stopped from break/step */ PC = plst->regs.REG_PC; /* Must check PC to see whether in situations where we had step emulation we are on a breakpoint or just have returned from an emulated single-step */ if (BreakIdentify (plst, 0 /*no adjust*/, -1 /*no thread*/) > 0) { hadStepEmul = 0; } plst->is_step = hadStepEmul || IS_STEP(plst->regs) || plst->last_start == LAST_START; DPRINTF (("TgtHandleChildChange: %s last_start %s\n", plst->is_step ? "step": "break", GetLastStartName (plst->last_start))); if ((plst->is_step || origHadStepEmul || stopWanted) && (plst->last_start == LAST_STEP || plst->last_start == LAST_STEPOFF || plst->last_start == LAST_RANGE)) { DPRINTF (("TgtHandleChildChange: restoring stepped-off bkpt\n")); BreakSteppedOff (plst); } if (plst->last_start == LAST_STEPOFF && (plst->is_step||origHadStepEmul)) { /* stepped off break and now need cont */ DPRINTF (("TgtHandleChildChange: auto-resuming after step-off\n")); plst->last_start = LAST_CONT; /* convert to normal cont */ if (!stopWanted) { if (TgtPtrace(RPT_CONT, pid, (char *)1, 0, NULL)) return True; /* tell people */ return(False); /* wait for change */ } DPRINTF (("TgtHandleChildChange: stop_wanted %d in step-off\n", stopWanted)); *status = STS_MAKESIG (stopWanted); return True; /* Stop and notify */ } base = plst->break_list ? ((BASE_BREAK*)plst->break_list) : ((BASE_BREAK*)NULL); /* now see if step in range */ if (plst->last_start == LAST_RANGE /* step in range */ && (plst->is_step || origHadStepEmul) /* not a breakpoint */ && PC >= base->range_start && PC <= base->range_end) { /* still in range, keep going */ if (stopWanted) { DPRINTF (("TgtHandleChildChange: stop_wanted %d in step-range\n", stopWanted)); } else { DPRINTF (("TgtHandleChildChange: Reservation at %x\n", plst->regs.REG_PC)); } } if (!plst->is_step) /* was break */ { bidx = BreakIdentify (plst, 1 /*adjust*/, plst->thread); if (bidx == 0) { DPRINTF (("TgtHandleChildChange: forwarding bkpt to kernel\n")); if (unexp) { *unexp = 1; } return False; } if (bidx < 0) { /* Unwanted breakpoint, must step it off */ ptrace_in pin; ptrace_out* out; if (origHadStepEmul) { DPRINTF (("TgtHandleChildChange: bkpt %x becomes step\n", plst->regs.REG_PC)); bidx = -bidx; plst->is_step = 1; base->clr_step = plst->break_list [bidx].type == BRKT_INSTR; base->last_break = bidx; return True; } if (stopWanted) { DPRINTF (("TgtHandleChildChange: stop_wanted %d at bkpt %x\n", stopWanted, plst->regs.REG_PC)); /* The PC has already been adjusted by BreakIdentify */ *status = STS_MAKESIG (stopWanted); return True; } /* All the handling is done in ptrace_2_svc() so call it */ bidx = -bidx; DPRINTF (("TgtHandleChildChange: last %d (%s) restarting bkpt %d\n", plst->last_start, GetLastStartName (plst->last_start), bidx)); base->clr_step = 1; base->last_break = bidx; /* remember which one */ plst->running = 0; /* So that ptrace is accepted */ pin.pid = plst->pid; if (plst->last_start == LAST_STEP) { pin.addr.req = RPT_SINGLESTEP; } else { pin.addr.req = RPT_CONT; } pin.addr.ptrace_addr_data_in_u.address = 1; pin.data = 0; pin.flags = PTRFLG_NON_OWNER; out = RPCGENSRVNAME(ptrace_2_svc) (&pin, NULL); if (out->result == 0) return False; /* Continue waiting */ DPRINTF(("TgtHandleChildChange: failed to restart bkpt!\n")); /* If something went wrong, just stop on breakpoint */ } } } /* else sig != SIGTRAP */ /* finally, fill in stop info in break point array base */ if (bidx > 0) { /* store break info */ /* will need to get off the break for SW breakpoints only */ base->clr_step = plst->break_list [bidx].type == BRKT_INSTR; base->last_break = bidx; /* remember which one */ } else if (base) { /* clear break info */ base->clr_step = False; /* not stopped on break */ base->last_break = 0; } /* decision to notify owner based on last_start */ } /* stopped */ else /* terminated */ { if (plst->last_start == LAST_START) { /* spawn failed */ TgtNotifyAll(idx, BMSG_EXEC_FAIL, 0, 0, -1, True); plst->running = False; /* not running - dead */ plst->state = *status; /* contains errno in high word */ return(False); } else if ((UCHAR)(plst->last_start & ~LAST_START) < (UCHAR)LAST_KILLED) plst->last_start = LAST_NONE; /* doesn't matter anymore */ else return(False); /* killed and detach already notified */ } return(True); /* stop and notify */ } #ifdef DDEBUG /* ----------------------------------------------------------------------- TgtDbgPtrace - debug version of ptrace. ----------------------------------------------------------------------- */ int TgtDbgPtrace(int request, PID pid, char *addr, int data, void *addr2) { int diag; DPRINTF (("TgtDbgPtrace: entered (%s (%d), %d, %x, %d, %x)\n", PtraceName (request), request, pid, (int) addr, data, (int) addr2)); if (request == RPT_WRITETEXT || request == RPT_WRITEDATA) { int i; DPRINTF (("TgtDbgPtrace:")); if (rdb_debug) { for (i = 0; i < data && i < 16; ++i) { printf (" %02x", ((char*) addr2) [i] & 0xFF); } printf ("\n"); } } diag = TgtRealPtrace (request, pid, addr, data, addr2); DPRINTF (("TgtDbgPtrace: returned %d (%x) errno %d\n", diag, diag, getErrno())); if (request == RPT_GETREGS || request == RPT_GETTHREADREGS || request == RPT_SETREGS || request == RPT_SETTHREADREGS) { /* Use DPRINTF() so as to have the id prefix */ DPRINTF (("TgtDbgPtrace: (%s) PC = %x, SP = %x, FP = %x\n", PtraceName (request), ((xdr_regs*)addr)->REG_PC, ((xdr_regs*)addr)->REG_SP, ((xdr_regs*)addr)->REG_FP)); } return(diag); } #endif /* DDEBUG */