275 lines
8.1 KiB
C
275 lines
8.1 KiB
C
|
/* This file handles the process manager's part of debugging, using the
|
||
|
* ptrace system call. Most of the commands are passed on to the system
|
||
|
* task for completion.
|
||
|
*
|
||
|
* The debugging commands available are:
|
||
|
* T_STOP stop the process
|
||
|
* T_OK enable tracing by parent for this process
|
||
|
* T_GETINS return value from instruction space
|
||
|
* T_GETDATA return value from data space
|
||
|
* T_GETUSER return value from user process table
|
||
|
* T_SETINS set value in instruction space
|
||
|
* T_SETDATA set value in data space
|
||
|
* T_SETUSER set value in user process table
|
||
|
* T_RESUME resume execution
|
||
|
* T_EXIT exit
|
||
|
* T_STEP set trace bit
|
||
|
* T_SYSCALL trace system call
|
||
|
* T_ATTACH attach to an existing process
|
||
|
* T_DETACH detach from a traced process
|
||
|
* T_SETOPT set trace options
|
||
|
* T_GETRANGE get range of values
|
||
|
* T_SETRANGE set range of values
|
||
|
*
|
||
|
* The T_OK, T_ATTACH, T_EXIT, and T_SETOPT commands are handled here, and the
|
||
|
* T_RESUME, T_STEP, T_SYSCALL, and T_DETACH commands are partially handled
|
||
|
* here and completed by the system task. The rest are handled entirely by the
|
||
|
* system task.
|
||
|
*/
|
||
|
|
||
|
#include "pm.h"
|
||
|
#include <minix/com.h>
|
||
|
#include <minix/callnr.h>
|
||
|
#include <sys/ptrace.h>
|
||
|
#include <sys/wait.h>
|
||
|
#include <signal.h>
|
||
|
#include "mproc.h"
|
||
|
|
||
|
/*===========================================================================*
|
||
|
* do_trace *
|
||
|
*===========================================================================*/
|
||
|
int do_trace()
|
||
|
{
|
||
|
register struct mproc *child;
|
||
|
struct ptrace_range pr;
|
||
|
int i, r, req;
|
||
|
|
||
|
req = m_in.m_lc_pm_ptrace.req;
|
||
|
|
||
|
/* The T_OK call is made by the child fork of the debugger before it execs
|
||
|
* the process to be traced. The T_ATTACH call is made by the debugger itself
|
||
|
* to attach to an existing process.
|
||
|
*/
|
||
|
switch (req) {
|
||
|
case T_OK: /* enable tracing by parent for this proc */
|
||
|
if (mp->mp_tracer != NO_TRACER) return(EBUSY);
|
||
|
|
||
|
mp->mp_tracer = mp->mp_parent;
|
||
|
mp->mp_reply.m_pm_lc_ptrace.data = 0;
|
||
|
return(OK);
|
||
|
|
||
|
case T_ATTACH: /* attach to an existing process */
|
||
|
if ((child = find_proc(m_in.m_lc_pm_ptrace.pid)) == NULL) return(ESRCH);
|
||
|
if (child->mp_flags & EXITING) return(ESRCH);
|
||
|
|
||
|
/* For non-root processes, user and group ID must match. */
|
||
|
if (mp->mp_effuid != SUPER_USER &&
|
||
|
(mp->mp_effuid != child->mp_effuid ||
|
||
|
mp->mp_effgid != child->mp_effgid ||
|
||
|
child->mp_effuid != child->mp_realuid ||
|
||
|
child->mp_effgid != child->mp_realgid)) return(EPERM);
|
||
|
|
||
|
/* Only root may trace system servers. */
|
||
|
if (mp->mp_effuid != SUPER_USER && (child->mp_flags & PRIV_PROC))
|
||
|
return(EPERM);
|
||
|
|
||
|
/* System servers may not trace anyone. They can use sys_trace(). */
|
||
|
if (mp->mp_flags & PRIV_PROC) return(EPERM);
|
||
|
|
||
|
/* Can't trace self, PM or VM. */
|
||
|
if (child == mp || child->mp_endpoint == PM_PROC_NR ||
|
||
|
child->mp_endpoint == VM_PROC_NR) return(EPERM);
|
||
|
|
||
|
/* Can't trace a process that is already being traced. */
|
||
|
if (child->mp_tracer != NO_TRACER) return(EBUSY);
|
||
|
|
||
|
child->mp_tracer = who_p;
|
||
|
child->mp_trace_flags = TO_NOEXEC;
|
||
|
|
||
|
sig_proc(child, SIGSTOP, TRUE /*trace*/, FALSE /* ksig */);
|
||
|
|
||
|
mp->mp_reply.m_pm_lc_ptrace.data = 0;
|
||
|
return(OK);
|
||
|
|
||
|
case T_STOP: /* stop the process */
|
||
|
/* This call is not exposed to user programs, because its effect can be
|
||
|
* achieved better by sending the traced process a signal with kill(2).
|
||
|
*/
|
||
|
return(EINVAL);
|
||
|
|
||
|
case T_READB_INS: /* special hack for reading text segments */
|
||
|
if (mp->mp_effuid != SUPER_USER) return(EPERM);
|
||
|
if ((child = find_proc(m_in.m_lc_pm_ptrace.pid)) == NULL) return(ESRCH);
|
||
|
if (child->mp_flags & EXITING) return(ESRCH);
|
||
|
|
||
|
r = sys_trace(req, child->mp_endpoint, m_in.m_lc_pm_ptrace.addr,
|
||
|
&m_in.m_lc_pm_ptrace.data);
|
||
|
if (r != OK) return(r);
|
||
|
|
||
|
mp->mp_reply.m_pm_lc_ptrace.data = m_in.m_lc_pm_ptrace.data;
|
||
|
return(OK);
|
||
|
|
||
|
case T_WRITEB_INS: /* special hack for patching text segments */
|
||
|
if (mp->mp_effuid != SUPER_USER) return(EPERM);
|
||
|
if ((child = find_proc(m_in.m_lc_pm_ptrace.pid)) == NULL) return(ESRCH);
|
||
|
if (child->mp_flags & EXITING) return(ESRCH);
|
||
|
|
||
|
#if 0
|
||
|
/* Should check for shared text */
|
||
|
|
||
|
/* Make sure the text segment is not used as a source for shared
|
||
|
* text.
|
||
|
*/
|
||
|
child->mp_ino = 0;
|
||
|
child->mp_dev = 0;
|
||
|
child->mp_ctime = 0;
|
||
|
#endif
|
||
|
|
||
|
r = sys_trace(req, child->mp_endpoint, m_in.m_lc_pm_ptrace.addr,
|
||
|
&m_in.m_lc_pm_ptrace.data);
|
||
|
if (r != OK) return(r);
|
||
|
|
||
|
mp->mp_reply.m_pm_lc_ptrace.data = m_in.m_lc_pm_ptrace.data;
|
||
|
return(OK);
|
||
|
}
|
||
|
|
||
|
/* All the other calls are made by the tracing process to control execution
|
||
|
* of the child. For all these calls, the child must be stopped.
|
||
|
*/
|
||
|
if ((child = find_proc(m_in.m_lc_pm_ptrace.pid)) == NULL) return(ESRCH);
|
||
|
if (child->mp_flags & EXITING) return(ESRCH);
|
||
|
if (child->mp_tracer != who_p) return(ESRCH);
|
||
|
if (!(child->mp_flags & TRACE_STOPPED)) return(EBUSY);
|
||
|
|
||
|
switch (req) {
|
||
|
case T_EXIT: /* exit */
|
||
|
child->mp_flags |= TRACE_EXIT;
|
||
|
|
||
|
/* Defer the exit if the traced process has an VFS call pending. */
|
||
|
if (child->mp_flags & VFS_CALL)
|
||
|
child->mp_exitstatus = m_in.m_lc_pm_ptrace.data; /* save it */
|
||
|
else
|
||
|
exit_proc(child, m_in.m_lc_pm_ptrace.data,
|
||
|
FALSE /*dump_core*/);
|
||
|
|
||
|
/* Do not reply to the caller until VFS has processed the exit
|
||
|
* request.
|
||
|
*/
|
||
|
return(SUSPEND);
|
||
|
|
||
|
case T_SETOPT: /* set trace options */
|
||
|
child->mp_trace_flags = m_in.m_lc_pm_ptrace.data;
|
||
|
|
||
|
mp->mp_reply.m_pm_lc_ptrace.data = 0;
|
||
|
return(OK);
|
||
|
|
||
|
case T_GETRANGE:
|
||
|
case T_SETRANGE: /* get/set range of values */
|
||
|
r = sys_datacopy(who_e, m_in.m_lc_pm_ptrace.addr, SELF, (vir_bytes)&pr,
|
||
|
(phys_bytes)sizeof(pr));
|
||
|
if (r != OK) return(r);
|
||
|
|
||
|
if (pr.pr_space != TS_INS && pr.pr_space != TS_DATA) return(EINVAL);
|
||
|
if (pr.pr_size == 0 || pr.pr_size > LONG_MAX) return(EINVAL);
|
||
|
|
||
|
if (req == T_GETRANGE)
|
||
|
r = sys_vircopy(child->mp_endpoint, (vir_bytes) pr.pr_addr,
|
||
|
who_e, (vir_bytes) pr.pr_ptr,
|
||
|
(phys_bytes) pr.pr_size, 0);
|
||
|
else
|
||
|
r = sys_vircopy(who_e, (vir_bytes) pr.pr_ptr,
|
||
|
child->mp_endpoint, (vir_bytes) pr.pr_addr,
|
||
|
(phys_bytes) pr.pr_size, 0);
|
||
|
|
||
|
if (r != OK) return(r);
|
||
|
|
||
|
mp->mp_reply.m_pm_lc_ptrace.data = 0;
|
||
|
return(OK);
|
||
|
|
||
|
case T_DETACH: /* detach from traced process */
|
||
|
if (m_in.m_lc_pm_ptrace.data < 0 || m_in.m_lc_pm_ptrace.data >= _NSIG)
|
||
|
return(EINVAL);
|
||
|
|
||
|
child->mp_tracer = NO_TRACER;
|
||
|
|
||
|
/* Let all tracer-pending signals through the filter. */
|
||
|
for (i = 1; i < _NSIG; i++) {
|
||
|
if (sigismember(&child->mp_sigtrace, i)) {
|
||
|
sigdelset(&child->mp_sigtrace, i);
|
||
|
check_sig(child->mp_pid, i, FALSE /* ksig */);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (m_in.m_lc_pm_ptrace.data > 0) { /* issue signal */
|
||
|
sig_proc(child, m_in.m_lc_pm_ptrace.data, TRUE /*trace*/,
|
||
|
FALSE /* ksig */);
|
||
|
}
|
||
|
|
||
|
/* Resume the child as if nothing ever happened. */
|
||
|
child->mp_flags &= ~TRACE_STOPPED;
|
||
|
child->mp_trace_flags = 0;
|
||
|
|
||
|
check_pending(child);
|
||
|
|
||
|
break;
|
||
|
|
||
|
case T_RESUME:
|
||
|
case T_STEP:
|
||
|
case T_SYSCALL: /* resume execution */
|
||
|
if (m_in.m_lc_pm_ptrace.data < 0 || m_in.m_lc_pm_ptrace.data >= _NSIG)
|
||
|
return(EINVAL);
|
||
|
|
||
|
if (m_in.m_lc_pm_ptrace.data > 0) { /* issue signal */
|
||
|
sig_proc(child, m_in.m_lc_pm_ptrace.data, FALSE /*trace*/,
|
||
|
FALSE /* ksig */);
|
||
|
}
|
||
|
|
||
|
/* If there are any other signals waiting to be delivered,
|
||
|
* feign a successful resumption.
|
||
|
*/
|
||
|
for (i = 1; i < _NSIG; i++) {
|
||
|
if (sigismember(&child->mp_sigtrace, i)) {
|
||
|
mp->mp_reply.m_pm_lc_ptrace.data = 0;
|
||
|
return(OK);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
child->mp_flags &= ~TRACE_STOPPED;
|
||
|
|
||
|
check_pending(child);
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
r = sys_trace(req, child->mp_endpoint, m_in.m_lc_pm_ptrace.addr,
|
||
|
&m_in.m_lc_pm_ptrace.data);
|
||
|
if (r != OK) return(r);
|
||
|
|
||
|
mp->mp_reply.m_pm_lc_ptrace.data = m_in.m_lc_pm_ptrace.data;
|
||
|
return(OK);
|
||
|
}
|
||
|
|
||
|
/*===========================================================================*
|
||
|
* trace_stop *
|
||
|
*===========================================================================*/
|
||
|
void trace_stop(rmp, signo)
|
||
|
register struct mproc *rmp;
|
||
|
int signo;
|
||
|
{
|
||
|
/* A traced process got a signal so stop it. */
|
||
|
|
||
|
register struct mproc *rpmp = mproc + rmp->mp_tracer;
|
||
|
int r;
|
||
|
|
||
|
r = sys_trace(T_STOP, rmp->mp_endpoint, 0L, (long *) 0);
|
||
|
if (r != OK) panic("sys_trace failed: %d", r);
|
||
|
|
||
|
rmp->mp_flags |= TRACE_STOPPED;
|
||
|
if (wait_test(rpmp, rmp)) {
|
||
|
sigdelset(&rmp->mp_sigtrace, signo);
|
||
|
|
||
|
rpmp->mp_flags &= ~WAITING; /* parent is no longer waiting */
|
||
|
rpmp->mp_reply.m_pm_lc_waitpid.status = W_STOPCODE(signo);
|
||
|
reply(rmp->mp_tracer, rmp->mp_pid);
|
||
|
}
|
||
|
}
|