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