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 + -
显示快捷键?