/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2010 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* DTrace Process Control
*
* This file provides a set of routines that permit libdtrace and its clients
* to create and grab process handles using libproc, and to share these handles
* between library mechanisms that need libproc access, such as ustack(), and
* client mechanisms that need libproc access, such as dtrace(1M) -c and -p.
* The library provides several mechanisms in the libproc control layer:
*
* Reference Counting: The library code and client code can independently grab
* the same process handles without interfering with one another. Only when
* the reference count drops to zero and the handle is not being cached (see
* below for more information on caching) will Prelease() be called on it.
*
* Handle Caching: If a handle is grabbed PGRAB_RDONLY (e.g. by ustack()) and
* the reference count drops to zero, the handle is not immediately released.
* Instead, libproc handles are maintained on dph_lrulist in order from most-
* recently accessed to least-recently accessed. Idle handles are maintained
* until a pre-defined LRU cache limit is exceeded, permitting repeated calls
* to ustack() to avoid the overhead of releasing and re-grabbing processes.
*
* Process Control: For processes that are grabbed for control (~PGRAB_RDONLY)
* or created by dt_proc_create(), a control thread is created to provide
* callbacks on process exit and symbol table caching on dlopen()s.
*
* MT-Safety: Libproc is not MT-Safe, so dt_proc_lock() and dt_proc_unlock()
* are provided to synchronize access to the libproc handle between libdtrace
* code and client code and the control thread's use of the ps_prochandle.
*
* NOTE: MT-Safety is NOT provided for libdtrace itself, or for use of the
* dtrace_proc_grab/dtrace_proc_create mechanisms. Like all exported libdtrace
* calls, these are assumed to be MT-Unsafe. MT-Safety is ONLY provided for
* synchronization between libdtrace control threads and the client thread.
*
* The ps_prochandles themselves are maintained along with a dt_proc_t struct
* in a hash table indexed by PID. This provides basic locking and reference
* counting. The dt_proc_t is also maintained in LRU order on dph_lrulist.
* The dph_lrucnt and dph_lrulim count the number of cacheable processes and
* the current limit on the number of actively cached entries.
*
* The control thread for a process establishes breakpoints at the rtld_db
* locations of interest, updates mappings and symbol tables at these points,
* and handles exec and fork (by always following the parent). The control
* thread automatically exits when the process dies or control is lost.
*
* A simple notification mechanism is provided for libdtrace clients using
* dtrace_handle_proc() for notification of PS_UNDEAD or PS_LOST events. If
* such an event occurs, the dt_proc_t itself is enqueued on a notification
* list and the control thread broadcasts to dph_cv. dtrace_sleep() will wake
* up using this condition and will then call the client handler as necessary.
*/
#include <strings.h>
#include <signal.h>
#include <assert.h>
#include <errno.h>
#include <dt_proc.h>
#include <dt_pid.h>
#include <dt_impl.h>
static dt_bkpt_t *
{
}
return (dbp);
}
static void
{
}
}
}
static void
{
break;
}
dt_dprintf("pid %d: spurious breakpoint wakeup for %lx\n",
return;
}
dt_dprintf("pid %d: hit breakpoint at %lx (%lu)\n",
}
static void
{
}
dt_dprintf("breakpoints enabled\n");
}
static void
{
}
dt_dprintf("breakpoints disabled\n");
}
static void
const char *msg)
{
dt_dprintf("failed to allocate notification for %d %s\n",
} else {
else
sizeof (dprn->dprn_errmsg));
}
}
/*
* Check to see if the control thread was requested to stop when the victim
* process reached a particular event (why) rather than continuing the victim.
* If 'why' is set in the stop mask, we wait on dpr_cv for dt_proc_continue().
* If 'why' is not set, this function returns immediately and does nothing.
*/
static void
{
/*
* We disable breakpoints while stopped to preserve the
* integrity of the program text for both our own disassembly
* and that of the kernel.
*/
}
}
/*ARGSUSED*/
static void
{
}
static void
{
dt_dprintf("pid %d: failed to get %s event message: %s\n",
return;
}
dt_dprintf("pid %d: rtld event %s type=%d state %d\n",
case RD_DLACTIVITY:
break;
dpr->dpr_errmsg);
break;
case RD_PREINIT:
break;
case RD_POSTINIT:
break;
}
}
static void
{
dt_dprintf("pid %d: failed to get event address for %s: %s\n",
return;
}
dt_dprintf("pid %d: event %s has unexpected type %d\n",
return;
}
}
/*
* Common code for enabling events associated with the run-time linker after
* attaching to a process or after a victim process completes an exec(2).
*/
static void
{
if (exec) {
return; /* exec failed: nothing needs to be done */
}
} else {
dt_dprintf("pid %d: failed to enable rtld events: %s\n",
"rtld_db agent initialization failed");
}
} else {
dt_dprintf("pid %d: failed to find a.out`main: %s\n",
}
}
/*
* Wait for a stopped process to be set running again by some other debugger.
* This is typically not required by /proc-based debuggers, since the usual
* model is that one debugger controls one victim. But DTrace, as usual, has
* its own needs: the stop() action assumes that prun(1) or some other tool
* will be applied to resume the victim process. This could be solved by
* adding a PCWRUN directive to /proc, but that seems like overkill unless
* other debuggers end up needing this functionality, so we implement a cheap
* equivalent to PCWRUN using the set of existing kernel mechanisms.
*
* Our intent is really not just to wait for the victim to run, but rather to
* wait for it to run and then stop again for a reason other than the current
* PR_REQUESTED stop. Since PCWSTOP/Pstopstatus() can be applied repeatedly
* to a stopped process and will return the same result without affecting the
* victim, we can just perform these operations repeatedly until Pstate()
* changes, the representative LWP ID changes, or the stop timestamp advances.
* dt_proc_control() will then rediscover the new state and continue as usual.
* When the process is still stopped in the same exact state, we sleep for a
* brief interval before waiting again so as not to spin consuming CPU cycles.
*/
static void
{
/*
* While we are waiting for the victim to run, clear PR_KLC and PR_RLC
* so that if the libdtrace client is killed, the victim stays stopped.
* dt_proc_destroy() will also observe this and perform PRELEASE_HANG.
*/
(void) Punsetflags(P, krflag);
Psync(P);
continue; /* check dpr_quit and continue waiting */
(void) Pstopstatus(P, PCNULL, 0);
/*
* If we've reached a new state, found a new representative, or
* original setting and then return with dpr_lock held.
*/
Psync(P);
return;
}
}
}
typedef struct dt_proc_control_data {
/*
* Main loop for all victim process control threads. We initialize all the
* appropriate /proc control mechanisms, and then enter a loop waiting for
* the process to stop on an event or die. We process any events by calling
* appropriate subroutines, and exit when the victim dies or we lose control.
*
* The control thread synchronizes the use of dpr_proc with other libdtrace
* threads using dpr_lock. We hold the lock for all of our operations except
* waiting while the process is running: this is accomplished by writing a
* PCWSTOP directive directly to the underlying /proc/<pid>/ctl file. If the
* libdtrace client wishes to exit or abort our wait, SIGCANCEL can be used.
*/
static void *
{
/*
* We disable the POSIX thread cancellation mechanism so that the
* client program using libdtrace can't accidentally cancel our thread.
* dt_proc_destroy() uses SIGCANCEL explicitly to simply poke us out
* of PCWSTOP with EINTR, at which point we will see dpr_quit and exit.
*/
/*
* Set up the corresponding process for tracing by libdtrace. We want
* to be able to catch breakpoints and efficiently single-step over
* them, and we need to enable librtld_db to watch libdl activity.
*/
/*
* We must trace exit from exec() system calls so that if the exec is
* successful, we can reset our breakpoints and re-initialize libproc.
*/
/*
* We must trace entry and exit for fork() system calls in order to
* disable our breakpoints temporarily during the fork. We do not set
* the PR_FORK flag, so if fork succeeds the child begins executing and
* does not inherit any other tracing behaviors or a control thread.
*/
Psync(P); /* enable all /proc changes */
/*
* If PR_KLC is set, we created the process; otherwise we grabbed it.
* Check for an appropriate stop request and wait for dt_proc_continue.
*/
else
if (Psetrun(P, 0, 0) == -1) {
dt_dprintf("pid %d: failed to set running: %s\n",
}
/*
* Wait for the process corresponding to this control thread to stop,
* process the event, and then set it running again. We want to sleep
* with dpr_lock *unheld* so that other parts of libdtrace can use the
* ps_prochandle in the meantime (e.g. ustack()). To do this, we write
* a PCWSTOP directive directly to the underlying /proc/<pid>/ctl file.
* Once the process stops, we wake up, grab dpr_lock, and then call
* Pwait() (which will return immediately) and do our processing.
*/
continue; /* check dpr_quit and continue waiting */
continue; /* check dpr_quit and continue waiting */
}
switch (Pstate(P)) {
case PS_STOP:
dt_dprintf("pid %d: proc stopped showing %d/%d\n",
/*
* If the process stops showing PR_REQUESTED, then the
* DTrace stop() action was applied to it or another
* debugging utility (e.g. pstop(1)) asked it to stop.
* In either case, the user's intention is for the
* process to remain stopped until another external
* mechanism (e.g. prun(1)) is applied. So instead of
* setting the process running ourself, we wait for
* someone else to do so. Once that happens, we return
* to our normal loop waiting for an event of interest.
*/
continue;
}
/*
* If the process stops showing one of the events that
* we are tracing, perform the appropriate response.
* Note that we ignore PR_SUSPENDED, PR_CHECKPOINT, and
* PR_JOBCONTROL by design: if one of these conditions
* occurs, we will fall through to Psetrun() but the
* process will remain stopped in the kernel by the
* corresponding mechanism (e.g. job control stop).
*/
break;
case PS_LOST:
if (Preopen(P) == 0)
goto pwait_locked;
dt_dprintf("pid %d: proc lost: %s\n",
break;
case PS_UNDEAD:
break;
}
dt_dprintf("pid %d: failed to set running: %s\n",
}
}
/*
* If the control thread detected PS_UNDEAD or PS_LOST, then enqueue
* the dt_proc_t structure on the dt_proc_hash_t notification list.
*/
if (notify)
/*
* Destroy and remove any remaining breakpoints, set dpr_done and clear
* dpr_tid to indicate the control thread has exited, and notify any
* waiting thread in dt_proc_destroy() that we have succesfully exited.
*/
return (NULL);
}
/*PRINTFLIKE3*/
static struct ps_prochandle *
{
return (NULL);
}
{
break;
else
}
if (remove)
return (dpr);
}
static void
{
int rflag;
/*
* If neither PR_KLC nor PR_RLC is set, then the process is stopped by
* an external debugger and we were waiting in dt_proc_waitrun().
* Leave the process in this condition using PRELEASE_HANG.
*/
} else {
rflag = 0; /* apply run-on-last-close */
}
/*
* Set the dpr_quit flag to tell the daemon thread to exit. We
* send it a SIGCANCEL to poke it out of PCWSTOP or any other
* long-term /proc system call. Our daemon threads have POSIX
* cancellation disabled, so EINTR will be the only effect. We
* then wait for dpr_done to indicate the thread has exited.
*
* We can't use pthread_kill() to send SIGCANCEL because the
* interface forbids it and we can't use pthread_cancel()
* because with cancellation disabled it won't actually
* send SIGCANCEL to the target thread, so we use _lwp_kill()
* to do the job. This is all built on evil knowledge of
* the details of the cancellation mechanism in libc.
*/
/*
* If the process is currently idling in dt_proc_stop(), re-
* enable breakpoints and poke it into running again.
*/
}
}
/*
* Before we free the process structure, remove this dt_proc_t from the
* lookup hash, and then walk the dt_proc_hash_t's notification list
* and remove this dt_proc_t if it is enqueued.
*/
} else {
}
}
/*
* Remove the dt_proc_list from the LRU list, release the underlying
* libproc handle, and free our dt_proc_t data structure.
*/
if (dpr->dpr_cacheable) {
dph->dph_lrucnt--;
}
}
static int
{
int err;
(void) pthread_attr_init(&a);
(void) pthread_attr_setdetachstate(&a, PTHREAD_CREATE_DETACHED);
(void) sigfillset(&nset);
/*
* If the control thread was created, then wait on dpr_cv for either
* dpr_done to be set (the victim died or the control thread failed)
* or DT_PROC_STOP_IDLE to be set, indicating that the victim is now
* stopped by /proc and the control thread is at the rendezvous event.
* On success, we return with the process and control thread stopped:
* the caller can then apply dt_proc_continue() to resume both.
*/
if (err == 0) {
/*
* If dpr_done is set, the control thread aborted before it
* reached the rendezvous event. This is either due to PS_LOST
* or PS_UNDEAD (i.e. the process died). We try to provide a
* small amount of useful information to help figure it out.
*/
"failed to control pid %d: process exec'd "
"set-id or unobservable program\n", pid);
} else if (WIFSIGNALED(stat)) {
"failed to control pid %d: process died "
} else {
"failed to control pid %d: process exited "
}
}
} else {
"failed to create control thread for process-id %d: %s\n",
}
(void) pthread_attr_destroy(&a);
return (err);
}
struct ps_prochandle *
{
int err;
return (NULL); /* errno is set for us */
}
return (NULL); /* dt_proc_error() has been called for us */
}
struct ps_prochandle *
{
int err;
/*
* Search the hash table for the pid. If it is already grabbed or
* created, move the handle to the front of the lrulist, increment
* the reference count, and return the existing ps_prochandle.
*/
/*
* If the cached handle was opened read-only and
* this request is for a writeable handle, mark
* the cached handle as stale and open a new handle.
* Since it's stale, unmark it as cacheable.
*/
dph->dph_lrucnt--;
break;
}
}
}
return (NULL); /* errno is set for us */
}
/*
* If we are attempting to grab the process without a monitor
* thread, then mark the process cacheable only if it's being
* grabbed read-only. If we're currently caching more process
* handles than dph_lrulim permits, attempt to find the
* least-recently-used handle that is currently unreferenced and
* release it from the cache. Otherwise we are grabbing the process
* for control: create a control thread for this process and store
* its ID in dpr->dpr_tid.
*/
break;
}
}
}
if (flags & PGRAB_RDONLY) {
dph->dph_lrucnt++;
}
return (NULL); /* dt_proc_error() has been called for us */
}
void
{
dt_proc_destroy(dtp, P);
}
void
{
}
}
void
{
}
void
{
}
void
{
}
}
void
{
}
struct ps_prochandle *
{
return (P);
}
struct ps_prochandle *
{
return (P);
}
void
{
dt_proc_release(dtp, P);
}
void
{
dt_proc_continue(dtp, P);
}