/* ********************************************************************** * * Component: RDBG * Module: servrpc.c * * Synopsis: support routines for RPC dispatch for remote debug server. * Main server dispatch routines from RPC to support remote debug. * ********************************************************************** */ #include #include #include #include #include /************************************************************************/ /* ----------------------------------------------------------------------- open_connex_2_svc - setup a new connection from a client. Notes: - this function creates a new connection to a client. It allocates an entry in the connection structure and fills in the information sent and implied by the message. - a client connection entry is needed for all further messages to work properly. ----------------------------------------------------------------------- */ open_out* RPCGENSRVNAME(open_connex_2_svc) (open_in *in, struct svc_req *rqstp) { static open_out out; /* output response. This could be heap local */ int idx; static int one_time = 0; /* we do one-time setup on back port */ /* need to support in->debug_type, in->flags, and in->destination!!! */ if (!one_time) { /* only setup one backport socket */ /* now setup signals and the like for handling process changes */ setErrno(0); TspInit(rqstp->rq_xprt->xp_sock); /* init transport system */ if (getErrno()) { /* failed in setup */ out.port = (u_long)-1; out.fp = getErrno(); /* error causing to fail */ return(&out); /* fail */ } one_time = True; /* disable doing this again */ } DPRINTF(("open_connex_2_svc: Opening connection from '%s'\n", in->user_name)); /* now setup a validation of all other connections */ for (idx = 0; idx < conn_list_cnt; idx++) if (conn_list[idx].in_use) { /* setup retry timer */ DPRINTF(("open_connex_2_svc: Still have connection %d with port %d\n", idx, HL_W(*((UINT16*)&conn_list[idx].back_port.c[2])))); } idx = ConnCreate(rqstp, in); /* setup the connection */ out.port = idx; /* connection number */ if (idx == -1) out.fp = getErrno(); /* error causing to fail */ else out.fp = TARGET_PROC_TYPE; out.server_vers = SERVER_VERS; return(&out); } /* ----------------------------------------------------------------------- send_signal_2_svc - send a kill/signal to the specified process. Notes: - this function sends a signal to the process specified. This process does not have to be under debug nor attached by this server. The kill may be refused on other grounds though. - kill(pid, 0) can be used to validate the process still exists if needed. ----------------------------------------------------------------------- */ signal_out *RPCGENSRVNAME(send_signal_2_svc) (signal_in *in, struct svc_req *rqstp) { static signal_out out; /* return code from kill */ /* we do not care if connected */ setErrno(0); out.kill_return = 0; out.errNo = 0; TotalReboot = 1; return(&out); } /* ----------------------------------------------------------------------- close_connex_2_svc - close a connection from a client. ----------------------------------------------------------------------- */ void *RPCGENSRVNAME(close_connex_2_svc) (close_in *in, struct svc_req *rqstp) { int conn_idx = TspConnGetIndex(rqstp); if (conn_idx != -1) /* found it, clear out */ ConnDelete(conn_idx, rqstp, in->control); return (void*) ""; /* need to return something */ } /* ----------------------------------------------------------------------- ptrace_2_svc - control process under debug. ----------------------------------------------------------------------- */ #define REG_COUNT \ (sizeof (xdr_regs) / sizeof (int)) ptrace_out *RPCGENSRVNAME(ptrace_2_svc) (ptrace_in *in, struct svc_req *rqstp) { int conn_idx = rqstp ? TspConnGetIndex(rqstp) : -1; static ptrace_out out; /* outut response (error or data) */ void *addr, *addr2; /* used for actual ptrace call */ unsigned int data; int req, pid, ret, pid_idx, idx; static union { /* local buffer for returned data */ Objects_Id t_list[UTHREAD_MAX]; /* thread_list return */ char t_name[THREADNAMEMAX]; /* thread name return */ } local_buff; /* for return handling of strings and the like */ PID_LIST *plst = NULL; /* current pid_list entry */ DPRINTF (("ptrace_2_svc: entered (%s (%d), %d, XXXX, %d, XXXX)\n", PtraceName (in->addr.req), in->addr.req, in->pid, in->data)); out.addr.ptrace_addr_data_out_u.addr = 0; /* validate the connection */ if (conn_idx == -1 && rqstp != NULL) { /* no connection, error */ DPRINTF(("ptrace_2_svc: msg from unknown debugger!\n")); out.result = -1; out.errNo = ECHILD; /* closest error */ out.addr.req = 0; /* to avoid copies that should not occur */ return(&out); } /* Consider that the last back-message is acknowledged */ if (conn_idx >= 0 && conn_list[conn_idx].retry) { TspMessageReceive(conn_idx, in->pid); } req = in->addr.req; out.addr.req = req; /* needed for RPC */ pid = in->pid; addr = addr2 = NULL; data = in->data; setErrno(0); /* assume works */ out.result = 0; /* assume worked ok */ out.errNo = 0; /* lookup process to make sure we have under control */ pid_idx = FindPidEntry (in->pid); if (pid_idx >= 0) /* found it */ { plst = &pid_list[pid_idx]; if (conn_idx < 0) conn_idx = plst->primary_conn; } /* now we handle the special case of ATTACH to a pid we already control */ if (req == RPT_ATTACH) { /* look it up first */ if (plst) { /* we have controlled , so return ok+show conn */ ret = 2; /* normally secondary connection */ if (! PIDMAP_TEST (conn_idx, pid_idx)) { /* mark as an owner if not already */ plst->owners++; PIDMAP_SET (conn_idx, pid_idx); /* mask in */ } else if (plst->primary_conn != NO_PRIMARY) { /* regrab makes primary */ /* Only if not primary already */ if (plst->primary_conn != conn_idx) { TspSendWaitChange(plst->primary_conn, BMSG_NOT_PRIM, conn_idx, plst->pid, 0, False); /* tell old owner */ } plst->primary_conn = NO_PRIMARY; } if (plst->primary_conn == NO_PRIMARY) { /* none now, so take over */ plst->primary_conn = conn_idx; /* new primary */ ret = 1; /* primary */ } out.result = ret; /* primary or secondary owner */ return(&out); } /* else attach process using target code */ setErrno(ESRCH); /* assume the worst */ if (!TgtAttach(conn_idx, pid)) { /* failed */ out.errNo = getErrno(); out.result = 0; } return(&out); } else if (req == RPT_DETACH) { /* see which kind of detach */ if (data == PTRDET_UNOWN) { /* only want to disconnect from */ TgtDetachCon(conn_idx, pid_idx, True); /* remove from control */ return(&out); /* done */ } } else if (plst && (req == RPT_GETNAME || req == RPT_GETBREAK)) { /* do nothing */ } else if (plst && req == RPT_CLRBREAK) { /* To be able to remove breakpoints from a "running" system */ DPRINTF (("ptrace_2_svc: allowing RPT_CLRBREAK %d\n", data)); /* do nothing */ } else if (plst && plst->running) { /* error, process is running and not detach */ out.result = -1; out.errNo = ETXTBSY; /* closest error */ DPRINTF (("ptrace_2_svc: failed, still running.\n")); return(&out); } if (plst == NULL) { out.result = -1; out.errNo = ESRCH; DPRINTF (("ptrace_2_svc: No such process.\n")); return (&out); } /* now make sure secondary owner is not trying to modify */ if (!(in->flags & PTRFLG_NON_OWNER)) /* if not overriden */ if (conn_idx != plst->primary_conn && ( (req >= RPT_POKETEXT && req <= RPT_SINGLESTEP) || (req >= RPT_SETREGS && req <= RPT_SETFPAREGS && (req & 1)) || (req >= RPT_SYSCALL && req <= RPT_DUMPCORE) || (req >= RPT_SETTARGETTHREAD && req <= RPT_THREADRESUME) || (req >= RPT_SETTHREADNAME && req <= RPT_SETTHREADREGS) || (req >= RPT_STEPRANGE && req <= RPT_CLRBREAK) || (req == RPT_STOP) || (req >= RPT_PSETREGS && req <= RPT_PSETTHREADREGS))) { /* not owner */ out.result = -1; out.errNo = EPERM; /* cannot alter as not primary */ DPRINTF (("ptrace_2_svc: refused, not owner, flags %d conn_idx %d primary_conn %d\n", in->flags, conn_idx, plst->primary_conn)); return(&out); } addr = (void *)in->addr.ptrace_addr_data_in_u.address; /* default */ /* now setup normal ptrace request by unpacking. May execute here. */ switch (req) { /* handle unpacking or setup for real call */ /* first the ones where addr points to input data */ case RPT_SETREGS: case RPT_SETTHREADREGS: addr = (void *)&in->addr.ptrace_addr_data_in_u.regs; /* reg list */ break; case RPT_PSETREGS: case RPT_PSETTHREADREGS: if (in->addr.ptrace_addr_data_in_u.pregs.pregs_len != REG_COUNT) { DPRINTF(("ptrace_2_svc: pid %d got %d expected %d\n", pid, in->addr.ptrace_addr_data_in_u.pregs.pregs_len, REG_COUNT)); setErrno(EINVAL); break; } req = req == RPT_PSETREGS ? RPT_SETREGS : RPT_SETTHREADREGS; addr = (void *) in->addr.ptrace_addr_data_in_u.pregs.pregs_val; break; case RPT_SETTHREADNAME: addr = (void *)in->addr.ptrace_addr_data_in_u.name; break; case RPT_WRITETEXT: case RPT_WRITEDATA: if ((int) data < 0) { setErrno(EINVAL); break; } addr = (void *)in->addr.ptrace_addr_data_in_u.mem.addr; /* targ addr */ addr2 = (void *)in->addr.ptrace_addr_data_in_u.mem.data; /* buff */ /* Forbid writing over breakpoints */ if (BreakOverwrite (plst, addr, data)) { setErrno(EBUSY); } break; case RPT_POKETEXT: case RPT_POKEDATA: /* Forbid writing over breakpoints */ if (BreakOverwrite (plst, addr, sizeof (int))) { setErrno(EBUSY); } break; /* now ones where we handle locally */ case RPT_GETTARGETTHREAD: out.result = plst->thread; req = 0; /* force exit */ break; case RPT_PGETREGS: /* return from our buffer */ out.addr.ptrace_addr_data_out_u.pregs.pregs_len = REG_COUNT; out.addr.ptrace_addr_data_out_u.pregs.pregs_val = (u_int*) &plst->regs; req = 0; /* force exit */ break; case RPT_GETREGS: /* return directly from our buffer */ /* this buffer is refreshed when changing target thread */ out.addr.ptrace_addr_data_out_u.regs = plst->regs; req = 0; /* force exit */ break; case RPT_SETBREAK: idx = BreakSet (plst, conn_idx, &in->addr.ptrace_addr_data_in_u.breakp); if (idx < 0) break; req = 0; /* force exit */ out.result = idx; /* return break index (>0) */ break; case RPT_CLRBREAK: if (conn_list[conn_idx].flags & DEBUGGER_IS_GDB) { data = BreakGetIndex (plst, addr); } out.result = BreakClear (plst, conn_idx, data); /* if errored, errno will still be set */ req = 0; break; case RPT_GETBREAK: /* data=handle, addr=in_buffer, returns next break. Data=0, returns cnt */ out.result = BreakGet (plst, data, &out.addr. ptrace_addr_data_out_u.breakp); req = 0; /* handle locally */ break; case RPT_GETNAME: /* get the name of the process */ if (!plst->name) out.addr.ptrace_addr_data_out_u.mem.dataNb = 0; else { int maxLen = sizeof out.addr.ptrace_addr_data_out_u.mem.data - 1; data = strlen(plst->name); if (data > maxLen) data = maxLen; out.addr.ptrace_addr_data_out_u.mem.dataNb = data+1; memcpy(out.addr.ptrace_addr_data_out_u.mem.data, plst->name, data+1); out.addr.ptrace_addr_data_out_u.mem.data [maxLen] = '\0'; } req = 0; break; case RPT_CONTTO: if (BreakSetAt (plst, conn_idx, (u_long) addr, BRKT_STEPEMUL) < 0) { DPRINTF(("ptrace_2_svc: BreakSet failed at %x", addr)); break; } req = RPT_CONT; /* data can contain a signal number, addr2 is unused */ goto case_RPT_CONT; case RPT_STEPRANGE: /* convert to step */ if (!data) data = 1; /* should we give an error?? */ BreakStepRange (plst, addr, data); if (getErrno()) break; req = RPT_SINGLESTEP; /* do by stepping */ addr = (void*) 1; /* start from current PC */ data = -2; /* want non-atomic stepping */ /* fall through to other exec cases */ case RPT_CONT: case_RPT_CONT: case RPT_SINGLESTEP: if (BreakStepOff (plst, &addr2)) { /* need clear then step off break */ /* clear break, step, then do exec */ if (addr == (void*) 1) addr = (void*) plst->regs.REG_PC;/* need for patch */ /* data is always 0, so atomic single-step */ } else if (req == RPT_SINGLESTEP) { data = -2; /* want non-atomic stepping */ } break; /* now ones where addr points to an output area */ case RPT_PGETTHREADREGS: addr = (void*) out.addr.ptrace_addr_data_out_u.mem.data; if (sizeof out.addr.ptrace_addr_data_out_u.mem.data < REG_COUNT * sizeof(int)) { setErrno(EINVAL); break; } if (data == plst->thread) { out.addr.ptrace_addr_data_out_u.pregs.pregs_len = REG_COUNT; out.addr.ptrace_addr_data_out_u.pregs.pregs_val = (u_int*) &plst->regs; req = 0; /* force exit */ break; } req = RPT_GETTHREADREGS; break; case RPT_GETTHREADREGS: addr = (void*) &out.addr.ptrace_addr_data_out_u.regs; break; case RPT_GETTHREADNAME: out.addr.ptrace_addr_data_out_u.name = local_buff.t_name; addr = (void*) out.addr.ptrace_addr_data_out_u.name; break; case RPT_THREADLIST: out.addr.ptrace_addr_data_out_u.threads.threads =(ptThreadList) local_buff.t_list; addr = (void*) out.addr.ptrace_addr_data_out_u.threads.threads; break; case RPT_READTEXT: case RPT_READDATA: if ((int) data < 0) { setErrno(EINVAL); break; } addr = (void *)in->addr.ptrace_addr_data_in_u.address; addr2 = (void *)out.addr.ptrace_addr_data_out_u.mem.data; out.addr.ptrace_addr_data_out_u.mem.dataNb = data; break; case RPT_DETACH: /* Do not allow detaching if breakpoints still there */ if (BreakGet (plst, 0, NULL)) { /* some bkpts still set */ setErrno(EINVAL); /* cannot detach safely */ break; } /* fall through */ case RPT_KILL: /* in the event they are trying to detach or kill a terminated process, we just delete the entry. */ if (PROC_TERMINATED (plst)) { TgtDelete(plst, -1, BMSG_KILLED); /* just blow off */ req = 0; /* now exit */ } break; } if (getErrno()) { /* failed in code above */ out.result = -1; out.errNo = getErrno(); DPRINTF(("ptrace_2_svc: result %d errNo %d\n", out.result, out.errNo)); return(&out); } else if (!req) { /* bail out now */ DPRINTF(("ptrace_2_svc: result %d errNo %d\n", out.result, out.errNo)); return(&out); } /* OK, make the call */ out.result = TgtPtrace(req, pid, addr, data, addr2); out.errNo = getErrno(); /* if no error, cleanup afterwards */ if (getErrno()) { /* Remove step-emul breakpoints if any */ if (req == RPT_SINGLESTEP || req == RPT_CONT) { BreakClear (plst, -1, -1); } DPRINTF(("ptrace_2_svc: result %d errNo %d\n", out.result, out.errNo)); return(&out); /* return error */ } switch (in->addr.req) { /* handle some special calls that affect state */ case RPT_CONT: case RPT_STEPRANGE: /* change to running */ if (in->addr.req == RPT_STEPRANGE) plst->last_start = LAST_RANGE; /* so range steps */ else if (addr2) plst->last_start = LAST_STEPOFF; /* now continue after wait */ else plst->last_start = LAST_CONT; plst->running = 1; /* mark as running */ if (!rqstp) /* Called internally to restart bkpt, no msg to anybody */ break; TgtNotifyAll(pid_idx, BMSG_WAIT, 0, 0, (in->flags & PTRFLG_NON_OWNER) ? -1 : conn_idx, True); break; case RPT_SINGLESTEP: /* mark as step */ plst->last_start = LAST_STEP; /* so we know how started */ plst->running = 1; /* mark as running (wait should catch fast) */ break; case RPT_DETACH: /* mark as disconnected */ case RPT_KILL: /* mark as killed */ if (in->flags & PTRFLG_FREE) /* notify and delete entry */ TgtDelete(plst, -1, (in->addr.req==RPT_KILL) ? BMSG_KILLED : BMSG_DETACH); else { /* notify and mark */ plst->last_start = (in->addr.req==RPT_KILL) ? LAST_KILLED : LAST_DETACHED; plst->state = -1; plst->running = False; TgtNotifyAll(pid_idx, (in->addr.req==RPT_KILL) ? BMSG_KILLED : BMSG_DETACH, 0, 0, -1, True); } break; case RPT_SETTHREADREGS: case RPT_PSETTHREADREGS: if (data != plst->thread) break; DPRINTF(("ptrace_2_svc: pid %d target thread regs changed!\n", pid)); case RPT_SETREGS: case RPT_PSETREGS: /* change our buffer as well */ if (plst->regs.REG_PC != ((xdr_regs*)addr)->REG_PC) BreakPcChanged (plst); plst->regs = *(xdr_regs*) addr; /* copy in */ break; /* case RPT_PGETREGS has been handled locally above */ case RPT_PGETTHREADREGS: /* We need to update pointer so that XDR works on return */ out.addr.ptrace_addr_data_out_u.pregs.pregs_len = REG_COUNT; out.addr.ptrace_addr_data_out_u.pregs.pregs_val = (void*) out.addr.ptrace_addr_data_out_u.mem.data; break; case RPT_PEEKTEXT: case RPT_PEEKDATA: case RPT_READDATA: case RPT_READTEXT: if (req < RPT_READDATA) { /* peek */ /* addr is start */ data = sizeof(int); addr2 = &out.result; /* data buffer */ /* Like read: addr is start, data is length, addr2 is buffer */ } BreakHide (plst, addr, data, addr2); break; case RPT_SETTARGETTHREAD: DPRINTF(("ptrace_2_svc: pid %d new target thread %d\n", pid, data)); TgtPtrace (RPT_GETREGS, pid, (char*) &plst->regs, 0, NULL); plst->thread = data; if (plst->break_list) { /* Forget we had to step off breakpoint */ BASE_BREAK* base = (BASE_BREAK*) plst->break_list; DPRINTF(("ptrace_2_svc: clr_step %d last_break %d\n", base->clr_step, base->last_break)); base->clr_step = 0; /* Not stopped on break */ base->last_break = 0; } break; case RPT_THREADLIST: out.addr.ptrace_addr_data_out_u.threads.nbThread = out.result; break; default: break; } /* end switch */ DPRINTF(("ptrace_2_svc 2: result %d errNo %d\n", out.result, out.errNo)); return(&out); } /* ----------------------------------------------------------------------- wait_info_2_svc - non-blocking wait request to check status. ----------------------------------------------------------------------- */ wait_out *RPCGENSRVNAME(wait_info_2_svc) (in, rqstp) wait_in *in; struct svc_req *rqstp; /* server info */ { int conn_idx = TspConnGetIndex(rqstp); static wait_out out; /* output of pid and status */ int idx; PID_LIST *plst; memset(&out, 0, sizeof(out)); /* zero for safety */ out.reason = STOP_ERROR; /* assume the worst */ if (conn_idx == -1) { /* no connection, error */ DPRINTF(("wait_info_2_svc: msg from unknown debugger!\n")); out.wait_return = -1; out.errNo = ECHILD; /* closest error */ return(&out); } else { /* see if confirming message received */ if (conn_list[conn_idx].retry) TspMessageReceive(conn_idx, in->pid); } if (!in->pid) { /* warm test verify only */ /* this call (pid==0) is made to confirm that that connection is still active. */ /* we let it fall through as an error since any use other than connection reset would be an error (there is no pid0). */ } else { /* normal request */ idx = FindPidEntry (in->pid); if (idx >= 0) { /* found process they requested on */ plst = &pid_list[idx]; out.wait_return = plst->running ? 0 : in->pid; /* return: 0 is running, pid is stopped/term */ out.errNo = 0; out.status = plst->state; /* last stopped reason if stopped */ out.thread = plst->thread;/* current thread (or -1 if none) from stop */ if (!out.wait_return) out.reason = STOP_NONE; /* running, no action */ else if (STS_SIGNALLED (out.status)) { /* stopped on signal */ out.handle = STS_GETSIG (out.status); /* signal number */ if (out.handle == SIGTRAP) if (plst->is_step) { /* single step with hitting a break */ out.reason = STOP_STEP; out.handle = 0; /* no information */ } else { /* stopped on break */ out.reason = STOP_BREAK; if (plst->break_list) out.handle = ((BASE_BREAK*)plst->break_list)->last_break; else out.handle = 0; /* no break */ } else out.reason = STOP_SIGNAL; out.PC = plst->regs.REG_PC; /* copy standard regs */ out.SP = plst->regs.REG_SP; out.FP = plst->regs.REG_FP; } else { /* terminated, so lower use count */ if (plst->last_start == LAST_KILLED) out.reason = STOP_KILLED; else if (plst->last_start == LAST_DETACHED) out.reason = STOP_DETACHED; else if (plst->last_start == LAST_START) { /* failed in exec */ out.reason = STOP_SPAWN_FAILED; out.handle = STS_GETCODE (out.status); /* errno reason */ } else if (STS_TERMONSIG (out.status)) { /* terminated on signal */ out.reason = STOP_TERM_SIG; /* mask off the core-dumped bit 7 */ out.handle = (int)(unsigned)(u_char) STS_TERMGETSIG (out.status); } else { /* exit(2)ed */ out.reason = STOP_TERM_EXIT; out.handle = STS_GETCODE (out.status); /* code */ } } DPRINTF(("wait_info_2_svc: pid %d return %d status %x errNo %d" " reason %d handle %d pc %x sp %x fp %x thread %d\n", in->pid, out.wait_return, out.status, out.errNo, out.reason, out.handle, out.PC, out.SP, out.FP, out.thread)); return(&out); } } /* if not found in list, we return error: no such process */ out.wait_return = -1; out.errNo = ESRCH; /* no process */ out.status = 0; return(&out); } /* ----------------------------------------------------------------------- get_signal_names_2_svc - return names for signals ----------------------------------------------------------------------- */ static one_signal SignalNames[] = { {SIGILL, "SIGILL/EVT_ILL"}, {SIGTRAP, "SIGTRAP/EVT_BKPT"}, {SIGFPE, "SIGFPE/EVT_FPE"}, {SIGKILL, "SIGKILL/EVT_AKILL"}, {SIGSEGV, "SIGSEGV/EVT_SEGV"}, {17, "SIGSTOP"}, {23, "SIGSTOP"} }; get_signal_names_out* RPCGENSRVNAME(get_signal_names_2_svc) (in, rqstp) void* in; struct svc_req *rqstp; /* server info */ { static get_signal_names_out out; out.signals.all_signals_len = sizeof SignalNames / sizeof SignalNames[0]; out.signals.all_signals_val = SignalNames; return(&out); }