xref: /illumos-gate/usr/src/lib/libtnfctl/internal.c (revision 7c478bd9)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright (c) 1994,1999 by Sun Microsystems, Inc.
24  * All rights reserved.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 /*
30  * Implements the routines that are needed only for internal process
31  * control.
32  */
33 
34 #ifndef DEBUG
35 #define	NDEBUG	1
36 #endif
37 
38 #include "tnfctl_int.h"
39 #include "kernel_int.h"
40 #include "dbg.h"
41 
42 #include <stdio.h>
43 #include <sys/types.h>
44 #include <stdlib.h>
45 #include <unistd.h>
46 #include <string.h>
47 #include <link.h>
48 #include <sys/stat.h>
49 #include <fcntl.h>
50 #include <sys/param.h>
51 #include <sys/procfs.h>
52 #include <assert.h>
53 #include <dlfcn.h>
54 
55 static int inprocess_read(void *ignore,
56     uintptr_t addr, void *buf, size_t size);
57 static int inprocess_write(void *ignore,
58     uintptr_t addr, void *buf, size_t size);
59 static pid_t inprocess_getpid(void *ignore);
60 static tnfctl_errcode_t inprocess_get_dtdebug(void *hndl, uintptr_t *ret_val);
61 static int inprocess_loadobj_iter(void *opq, tnfctl_ind_obj_f *obj_func,
62 					void *cd);
63 
64 /*
65  * Cause interposition on _dlclose(), dlclose(), _dlopen(), and dlopen()
66  */
67 #pragma weak _dlclose = _tnfctl_dlclose
68 #pragma weak dlclose = _tnfctl_dlclose
69 
70 #pragma weak _dlopen = _tnfctl_dlopen
71 #pragma weak dlopen = _tnfctl_dlopen
72 
73 /*
74  * The lock used to protect the _tnfctl_internal_tracing_flag variable.
75  *
76  */
77 mutex_t		_tnfctl_internalguard_lock = DEFAULTMUTEX;
78 boolean_t	_tnfctl_internal_tracing_flag = 0;
79 pid_t		_tnfctl_externally_traced_pid = NOPID;
80 
81 /*
82  * Returns a pointer to a tnfctl handle that can do in process probe control.
83  */
84 tnfctl_errcode_t
85 tnfctl_internal_open(tnfctl_handle_t **ret_val)
86 {
87 	tnfctl_handle_t	*hdl;
88 	tnfctl_errcode_t	prexstat;
89 	uintptr_t		dbgaddr;
90 
91 	/* allocate hdl and zero fill */
92 	hdl = calloc(1, sizeof (*hdl));
93 	if (hdl == NULL) {
94 		return (TNFCTL_ERR_ALLOCFAIL);
95 	}
96 
97 	hdl->mode = INTERNAL_MODE;
98 	hdl->called_exit = B_FALSE;
99 
100 	/* plug in inprocess call back functions */
101 	hdl->p_read = inprocess_read;
102 	hdl->p_write = inprocess_write;
103 	hdl->p_obj_iter = inprocess_loadobj_iter;
104 	hdl->p_getpid = inprocess_getpid;
105 
106 	/*
107 	 * get the address of DT_DEBUG and store it in proc_p
108 	 * (the handle on the same process is the dbg address)
109 	 */
110 	prexstat = inprocess_get_dtdebug(hdl, &dbgaddr);
111 	if (prexstat) {
112 		free(hdl);
113 		return (prexstat);
114 	}
115 	hdl->proc_p = (void *) dbgaddr;
116 
117 	/* initialize state in handle */
118 	prexstat = _tnfctl_set_state(hdl);
119 	if (prexstat) {
120 		free(hdl);
121 		return (prexstat);
122 	}
123 	/* see if process is already being traced */
124 	prexstat = _tnfctl_internal_getlock();
125 	if (prexstat) {
126 		free(hdl);
127 		return (prexstat);
128 	}
129 	*ret_val = hdl;
130 	return (TNFCTL_ERR_NONE);
131 }
132 
133 /*
134  * reads a block of memory from the same address space.
135  */
136 static int
137 inprocess_read(void *ignore, uintptr_t addr, void *buf, size_t size)
138 {
139 
140 	DBG_TNF_PROBE_2(inprocess_read_1, "libtnfctl", "sunw%verbosity 3;",
141 		tnf_long, num_bytes, size,
142 		tnf_opaque, from_address, addr);
143 
144 	(void) memcpy(buf, (void *) addr, size);
145 	return (0);
146 }
147 
148 /*
149  * writes a block of memory to the same address space.
150  */
151 static int
152 inprocess_write(void *ignore, uintptr_t addr, void *buf, size_t size)
153 {
154 
155 	DBG_TNF_PROBE_2(inprocess_write_1, "libtnfctl", "sunw%verbosity 3;",
156 		tnf_long, num_bytes, size,
157 		tnf_opaque, to_address, addr);
158 
159 	(void) memcpy((void *)addr, buf, size);
160 	return (0);
161 }
162 
163 /*
164  * returns the pid of the process.
165  */
166 static pid_t
167 inprocess_getpid(void *ignore)
168 {
169 	return (getpid());
170 }
171 extern Elf3264_Dyn _DYNAMIC;
172 
173 /*
174  * returns the address of the DT_DEBUG field in the _DYNAMIC array
175  * of the same address space.
176  */
177 static tnfctl_errcode_t
178 inprocess_get_dtdebug(void *hndl, uintptr_t *ret_val)
179 {
180 	Elf3264_Dyn 	*dyn = &_DYNAMIC;
181 	Elf3264_Dyn	*dp;
182 
183 	for (dp = dyn; dp->d_tag != DT_NULL; dp++) {
184 		if (dp->d_tag == DT_DEBUG) {
185 			*ret_val = (uintptr_t) dp;
186 			return (TNFCTL_ERR_NONE);
187 		}
188 	}
189 	return (TNFCTL_ERR_INTERNAL);
190 }
191 
192 #define	PROCFORMAT	"/proc/%d"
193 
194 /*
195  * iterate over all loadobjects in the same address space calling the
196  * callback function "obj_func".
197  */
198 static int
199 inprocess_loadobj_iter(void *opq, tnfctl_ind_obj_f *obj_func, void *cd)
200 {
201 	Elf3264_Dyn	*dtdebug = opq;
202 	struct r_debug	*r_dbg;
203 	struct link_map *lmap;
204 	char		path[MAXPATHLEN];
205 	int		procfd;
206 	tnfctl_ind_obj_info_t	loadobj;
207 	int		retval = 0;	/* sucessful return */
208 
209 	DBG_TNF_PROBE_0(inprocess_loadobj_iter_start, "libtnfctl",
210 			"start inprocess_loadobj_iter; sunw%verbosity 1");
211 
212 	r_dbg = (struct r_debug *)dtdebug->d_un.d_ptr;
213 
214 	DBG_TNF_PROBE_1(inprocess_loadobj_iter_1, "libtnfctl",
215 		"sunw%verbosity 1",
216 		tnf_string, link_map_state,
217 		(r_dbg->r_state == RT_CONSISTENT) ? "RT_CONSISTENT" :
218 			(r_dbg->r_state == RT_ADD) ? "RT_ADD" : "RT_DELETE");
219 
220 	/* bail if link map is not consistent */
221 	if (r_dbg->r_state != RT_CONSISTENT)
222 		return (1);
223 
224 	(void) sprintf(path, PROCFORMAT, (int) getpid());
225 
226 	/*
227 	 * opening /proc readonly, so debuggers can still run
228 	 * We use /proc in order to get fd on the object.
229 	 */
230 	procfd = open(path, O_RDONLY);
231 	if (procfd == -1)
232 		return (1);
233 
234 	for (lmap = r_dbg->r_map; lmap; lmap = lmap->l_next) {
235 		loadobj.text_base = lmap->l_addr;
236 		loadobj.data_base = lmap->l_addr;
237 		loadobj.objname = lmap->l_name;
238 		/*
239 		 * client of this interface should deal with -1 for objfd,
240 		 * so no error checking is needed on this ioctl
241 		 */
242 		loadobj.objfd = ioctl(procfd, PIOCOPENM, &(lmap->l_addr));
243 
244 		retval = obj_func(opq, &loadobj, cd);
245 
246 		/* close the fd */
247 		if (loadobj.objfd != -1)
248 			close(loadobj.objfd);
249 
250 		/* check for error */
251 		if (retval == 1)
252 			goto end_of_func;
253 	}
254 
255 end_of_func:
256 	close(procfd);
257 
258 	DBG_TNF_PROBE_0(inprocess_loadobj_iter_end, "libtnfctl",
259 			"end inprocess_loadobj_iter; sunw%verbosity 1");
260 	return (retval);
261 }
262 
263 /*
264  * The lock that prevents a thread from accessing our cached library list
265  * and a dlopen or dlclose happening at the same time in another thread.
266  */
267 mutex_t		_tnfctl_lmap_lock = DEFAULTMUTEX;
268 
269 /*
270  * The flag that indicates that the library list has changed via a
271  * dlopen or dlclose.
272  */
273 boolean_t	_tnfctl_libs_changed = B_FALSE;
274 
275 /*
276  * Thread id of the owner of the lock in order to implement a
277  * recursive lock i.e. no deadlock if the same thread tries to lock
278  * a lock it already holds.
279  */
280 static thread_t	lock_holder = 0;	/* XXX - no tid with 0 */
281 NOTE(MUTEX_PROTECTS_DATA(warlock::lmap_lock, lock_holder))
282 NOTE(DATA_READABLE_WITHOUT_LOCK(lock_holder))
283 
284 /*
285  * In the routines below, we will appear to use a different lock if we
286  * are running lock_lint/warlock.  We define a macro to represent whichever
287  * lock is appropriate.
288  */
289 #if defined(__lock_lint)
290 #define	LMAP_LOCK	(&warlock_kludge->lmap_lock)
291 #else
292 #define	LMAP_LOCK	(&_tnfctl_lmap_lock)
293 #endif
294 
295 /*
296  * dlclose interposition with a recursive lock so that a .fini section
297  * can recursively call dlopen or dlclose while holding _tnfctl_lmap_lock
298  * This interposition serializes access to rtld's loadobject list and
299  * also updates the flag _tnfctl_libs_changed to indicate a change in
300  * the library list.  This flag is checked by operations that update
301  * probes so that it can sync up with the new library list and potential
302  * new/deleted probes.
303  */
304 int
305 _tnfctl_dlclose(void *handle)
306 {
307 	static int (*real_dlclose)(void *handle) = NULL;
308 	int retval;
309 	thread_t tid;
310 
311 	if (real_dlclose == NULL) {
312 		real_dlclose = (int (*)(void *)) dlsym(RTLD_NEXT, "dlclose");
313 	}
314 	assert(real_dlclose);
315 
316 	if (mutex_trylock(LMAP_LOCK) != 0) {
317 		/* don't have lock */
318 		tid = thr_self();
319 		if (tid == lock_holder) {
320 			/* recursive dlopen/dlclose by same thread */
321 			return ((*real_dlclose)(handle));
322 		}
323 		/* not a recursive dlopen/dlclose - wait on lock */
324 		mutex_lock(LMAP_LOCK);
325 	}
326 
327 	/* lock is held now */
328 	lock_holder = thr_self();
329 	retval = (*real_dlclose)(handle);
330 
331 	/*
332 	 * reset lock_holder so that if _tnfctl_lmap_lock is held by some
333 	 * other part of the code, we don't assume it is a recursive
334 	 * dlopen/dlclose
335 	 */
336 	lock_holder = 0;
337 	_tnfctl_libs_changed = B_TRUE;
338 	mutex_unlock(LMAP_LOCK);
339 
340 	return (retval);
341 }
342 
343 /*
344  * dlopen interposition with a recursive lock so that a .init section
345  * can recursively call dlopen or dlclose while holding _tnfctl_lmap_lock
346  * This interposition serializes access to rtld's loadobject list and
347  * also updates the flag _tnfctl_libs_changed to indicate a change in
348  * the library list.  This flag is checked by operations that update
349  * probes so that it can sync up with the new library list and potential
350  * new/deleted probes.
351  */
352 void *
353 _tnfctl_dlopen(const char *pathname, int mode)
354 {
355 	static void * (*real_dlopen)(const char *, int) = NULL;
356 	void *retval;
357 	thread_t tid;
358 
359 	if (real_dlopen == NULL) {
360 		real_dlopen = (void * (*)(const char *, int))
361 					dlsym(RTLD_NEXT, "dlopen");
362 	}
363 	assert(real_dlopen);
364 
365 	if (mutex_trylock(LMAP_LOCK) != 0) {
366 		/* don't have lock */
367 		tid = thr_self();
368 		if (tid == lock_holder) {
369 			/* recursive dlopen/dlclose by same thread */
370 			return ((*real_dlopen)(pathname, mode));
371 		}
372 		/* not a recursive dlopen/dlclose - wait on lock */
373 		mutex_lock(LMAP_LOCK);
374 	}
375 
376 	/* lock is held now */
377 	lock_holder = thr_self();
378 	retval = (*real_dlopen)(pathname, mode);
379 
380 	/*
381 	 * reset lock_holder so that if _tnfctl_lmap_lock is held by some
382 	 * other part of the code, we don't assume it is a recursive
383 	 * dlopen/dlclose
384 	 */
385 	lock_holder = 0;
386 	_tnfctl_libs_changed = B_TRUE;
387 	mutex_unlock(LMAP_LOCK);
388 
389 	return (retval);
390 }
391 
392 tnfctl_errcode_t
393 _tnfctl_internal_getlock()
394 {
395 	mutex_lock(&_tnfctl_internalguard_lock);
396 	if (_tnfctl_internal_tracing_flag == 1) {
397 	/* internal trace control active */
398 	mutex_unlock(&_tnfctl_internalguard_lock);
399 	return (TNFCTL_ERR_BUSY);
400 	}
401 	_tnfctl_internal_tracing_flag = 1;
402 	if (_tnfctl_externally_traced_pid == getpid()) {
403 	/* external trace control is active */
404 	_tnfctl_internal_tracing_flag = 0;
405 	mutex_unlock(&_tnfctl_internalguard_lock);
406 	return (TNFCTL_ERR_BUSY);
407 	}
408 	DBG((void) fprintf(stderr, "_tnfctl_internal_getlock: ok to trace %d\n",
409 	getpid()));
410 	mutex_unlock(&_tnfctl_internalguard_lock);
411 	return (TNFCTL_ERR_NONE);
412 }
413 
414 
415 #ifdef __lock_lint
416 
417 /*
418  * dummy function for lock_lint (warlock) static lock analysis.
419  */
420 int
421 warlock_dummy()
422 {
423 	int (*fp)();
424 
425 	return ((*fp)());
426 }
427 
428 #endif
429