dt_proc.c
来自「Sun Solaris 10 中的 DTrace 组件的源代码。请参看: htt」· C语言 代码 · 共 1,011 行 · 第 1/3 页
C
1,011 行
/* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only. * See the file usr/src/LICENSING.NOTICE in this distribution or * http://www.opensolaris.org/license/ for details. */#pragma ident "@(#)dt_proc.c 1.6 04/11/17 SMI"/* * 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 <sys/wait.h>#include <strings.h>#include <signal.h>#include <assert.h>#include <errno.h>#include <dt_proc.h>#include <dt_impl.h>#define IS_SYS_EXEC(w) (w == SYS_exec || w == SYS_execve)#define IS_SYS_FORK(w) (w == SYS_vfork || w == SYS_fork1 || w == SYS_forkall)static dt_bkpt_t *dt_proc_bpcreate(dt_proc_t *dpr, uintptr_t addr, dt_bkpt_f *func, void *data){ struct ps_prochandle *P = dpr->dpr_proc; dt_bkpt_t *dbp; assert(DT_MUTEX_HELD(&dpr->dpr_lock)); if ((dbp = dt_zalloc(dpr->dpr_hdl, sizeof (dt_bkpt_t))) != NULL) { dbp->dbp_func = func; dbp->dbp_data = data; dbp->dbp_addr = addr; if (Psetbkpt(P, dbp->dbp_addr, &dbp->dbp_instr) == 0) dbp->dbp_active = B_TRUE; dt_list_append(&dpr->dpr_bps, dbp); } return (dbp);}static voiddt_proc_bpdestroy(dt_proc_t *dpr, int delbkpts){ int state = Pstate(dpr->dpr_proc); dt_bkpt_t *dbp, *nbp; assert(DT_MUTEX_HELD(&dpr->dpr_lock)); for (dbp = dt_list_next(&dpr->dpr_bps); dbp != NULL; dbp = nbp) { if (delbkpts && dbp->dbp_active && state != PS_LOST && state != PS_UNDEAD) { (void) Pdelbkpt(dpr->dpr_proc, dbp->dbp_addr, dbp->dbp_instr); } nbp = dt_list_next(dbp); dt_list_delete(&dpr->dpr_bps, dbp); dt_free(dpr->dpr_hdl, dbp); }}static voiddt_proc_bpmatch(dt_proc_t *dpr){ const lwpstatus_t *psp = &Pstatus(dpr->dpr_proc)->pr_lwp; dt_bkpt_t *dbp; assert(DT_MUTEX_HELD(&dpr->dpr_lock)); for (dbp = dt_list_next(&dpr->dpr_bps); dbp != NULL; dbp = dt_list_next(dbp)) { if (psp->pr_reg[R_PC] == dbp->dbp_addr) break; } if (dbp == NULL) { dt_dprintf("pid %d: spurious breakpoint wakeup for %lx\n", (int)dpr->dpr_pid, (ulong_t)psp->pr_reg[R_PC]); return; } dt_dprintf("pid %d: hit breakpoint at %lx (%lu)\n", (int)dpr->dpr_pid, (ulong_t)dbp->dbp_addr, ++dbp->dbp_hits); dbp->dbp_func(dpr, dbp->dbp_data); (void) Pxecbkpt(dpr->dpr_proc, dbp->dbp_instr);}static voiddt_proc_bpenable(dt_proc_t *dpr){ dt_bkpt_t *dbp; assert(DT_MUTEX_HELD(&dpr->dpr_lock)); for (dbp = dt_list_next(&dpr->dpr_bps); dbp != NULL; dbp = dt_list_next(dbp)) { if (!dbp->dbp_active && Psetbkpt(dpr->dpr_proc, dbp->dbp_addr, &dbp->dbp_instr) == 0) dbp->dbp_active = B_TRUE; }}static voiddt_proc_bpdisable(dt_proc_t *dpr){ dt_bkpt_t *dbp; assert(DT_MUTEX_HELD(&dpr->dpr_lock)); for (dbp = dt_list_next(&dpr->dpr_bps); dbp != NULL; dbp = dt_list_next(dbp)) { if (dbp->dbp_active && Pdelbkpt(dpr->dpr_proc, dbp->dbp_addr, dbp->dbp_instr) == 0) dbp->dbp_active = B_FALSE; }}/* * 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 voiddt_proc_stop(dt_proc_t *dpr, uint8_t why){ assert(DT_MUTEX_HELD(&dpr->dpr_lock)); assert(why != DT_PROC_STOP_IDLE); if (dpr->dpr_stop & why) { dpr->dpr_stop |= DT_PROC_STOP_IDLE; dpr->dpr_stop &= ~why; (void) pthread_cond_broadcast(&dpr->dpr_cv); while (dpr->dpr_stop & DT_PROC_STOP_IDLE) (void) pthread_cond_wait(&dpr->dpr_cv, &dpr->dpr_lock); }}static voiddt_proc_bpmain(dt_proc_t *dpr, const char *fname){ dt_dprintf("pid %d: breakpoint at %s()\n", (int)dpr->dpr_pid, fname); dt_proc_stop(dpr, DT_PROC_STOP_MAIN);}static voiddt_proc_rdevent(dt_proc_t *dpr, const char *evname){ rd_event_msg_t rdm; rd_err_e err; if ((err = rd_event_getmsg(dpr->dpr_rtld, &rdm)) != RD_OK) { dt_dprintf("pid %d: failed to get %s event message: %s\n", (int)dpr->dpr_pid, evname, rd_errstr(err)); return; } dt_dprintf("pid %d: rtld event %s type=%d state %d\n", (int)dpr->dpr_pid, evname, rdm.type, rdm.u.state); switch (rdm.type) { case RD_DLACTIVITY: if (rdm.u.state == RD_CONSISTENT) Pupdate_syms(dpr->dpr_proc); break; case RD_PREINIT: Pupdate_syms(dpr->dpr_proc); dt_proc_stop(dpr, DT_PROC_STOP_PREINIT); break; case RD_POSTINIT: Pupdate_syms(dpr->dpr_proc); dt_proc_stop(dpr, DT_PROC_STOP_POSTINIT); break; }}static voiddt_proc_rdwatch(dt_proc_t *dpr, rd_event_e event, const char *evname){ rd_notify_t rdn; rd_err_e err; if ((err = rd_event_addr(dpr->dpr_rtld, event, &rdn)) != RD_OK) { dt_dprintf("pid %d: failed to get event address for %s: %s\n", (int)dpr->dpr_pid, evname, rd_errstr(err)); return; } if (rdn.type != RD_NOTIFY_BPT) { dt_dprintf("pid %d: event %s has unexpected type %d\n", (int)dpr->dpr_pid, evname, rdn.type); return; } (void) dt_proc_bpcreate(dpr, rdn.u.bptaddr, (dt_bkpt_f *)dt_proc_rdevent, (void *)evname);}/* * 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 voiddt_proc_attach(dt_proc_t *dpr, int exec){ const pstatus_t *psp = Pstatus(dpr->dpr_proc); rd_err_e err; GElf_Sym sym; assert(DT_MUTEX_HELD(&dpr->dpr_lock)); if (exec) { if (psp->pr_lwp.pr_errno != 0) return; /* exec failed: nothing needs to be done */ dt_proc_bpdestroy(dpr, B_FALSE); Preset_maps(dpr->dpr_proc); } if ((dpr->dpr_rtld = Prd_agent(dpr->dpr_proc)) != NULL && (err = rd_event_enable(dpr->dpr_rtld, B_TRUE)) == RD_OK) { dt_proc_rdwatch(dpr, RD_PREINIT, "RD_PREINIT"); dt_proc_rdwatch(dpr, RD_POSTINIT, "RD_POSTINIT"); dt_proc_rdwatch(dpr, RD_DLACTIVITY, "RD_DLACTIVITY"); } else { dt_dprintf("pid %d: failed to enable rtld events: %s\n", (int)dpr->dpr_pid, dpr->dpr_rtld ? rd_errstr(err) : "rtld_db agent initialization failed"); } Pupdate_maps(dpr->dpr_proc); if (Pxlookup_by_name(dpr->dpr_proc, LM_ID_BASE, "a.out", "main", &sym, NULL) == 0) { (void) dt_proc_bpcreate(dpr, (uintptr_t)sym.st_value, (dt_bkpt_f *)dt_proc_bpmain, "a.out`main"); } else { dt_dprintf("pid %d: failed to find a.out`main: %s\n", (int)dpr->dpr_pid, strerror(errno)); }}/* * 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 voiddt_proc_waitrun(dt_proc_t *dpr){ struct ps_prochandle *P = dpr->dpr_proc; const lwpstatus_t *psp = &Pstatus(P)->pr_lwp; int krflag = psp->pr_flags & (PR_KLC | PR_RLC); timestruc_t tstamp = psp->pr_tstamp; lwpid_t lwpid = psp->pr_lwpid; const long wstop = PCWSTOP; int pfd = Pctlfd(P); assert(DT_MUTEX_HELD(&dpr->dpr_lock)); assert(psp->pr_flags & PR_STOPPED); assert(Pstate(P) == PS_STOP);
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?