xref: /illumos-gate/usr/src/cmd/mdb/common/mdb/mdb_tdb.c (revision ed093b41)
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) 2000-2001 by Sun Microsystems, Inc.
24  * All rights reserved.
25  */
26 
27 /*
28  * libthread_db (tdb) cache
29  *
30  * In order to properly debug multi-threaded programs, the proc target must be
31  * able to query and modify information such as a thread's register set using
32  * either the native LWP services provided by libproc (if the process is not
33  * linked with libthread), or using the services provided by libthread_db (if
34  * the process is linked with libthread).  Additionally, a process may begin
35  * life as a single-threaded process and then later dlopen() libthread, so we
36  * must be prepared to switch modes on-the-fly.  There are also two possible
37  * libthread implementations (one in /usr/lib and one in /usr/lib/lwp) so we
38  * cannot link mdb against libthread_db directly; instead, we must dlopen the
39  * appropriate libthread_db on-the-fly based on which libthread.so the victim
40  * process has open.  Finally, mdb is designed so that multiple targets can be
41  * active simultaneously, so we could even have *both* libthread_db's open at
42  * the same time.  This might happen if you were looking at two multi-threaded
43  * user processes inside of a crash dump, one using /usr/lib/libthread.so and
44  * the other using /usr/lib/lwp/libthread.so.  To meet these requirements, we
45  * implement a libthread_db "cache" in this file.  The proc target calls
46  * mdb_tdb_load() with the pathname of a libthread_db to load, and if it is
47  * not already open, we dlopen() it, look up the symbols we need to reference,
48  * and fill in an ops vector which we return to the caller.  Once an object is
49  * loaded, we don't bother unloading it unless the entire cache is explicitly
50  * flushed.  This mechanism also has the nice property that we don't bother
51  * loading libthread_db until we need it, so the debugger starts up faster.
52  */
53 
54 #include <mdb/mdb_tdb.h>
55 #include <mdb/mdb_modapi.h>
56 #include <mdb/mdb_err.h>
57 
58 #include <strings.h>
59 #include <unistd.h>
60 #include <dlfcn.h>
61 #include <link.h>
62 
63 static mdb_tdb_lib_t *tdb_list;
64 
65 static td_err_e
tdb_notsup()66 tdb_notsup()
67 {
68 	return (TD_NOCAPAB); /* return thread_db code for not supported */
69 }
70 
71 const mdb_tdb_ops_t *
mdb_tdb_load(const char * path)72 mdb_tdb_load(const char *path)
73 {
74 	td_err_e (*tdb_init)(void);
75 	mdb_tdb_lib_t *t;
76 	td_err_e err;
77 	void *hdl;
78 
79 	/*
80 	 * Search through the existing cache of thread_db libraries and see if
81 	 * we have this one loaded already.  If so, just return its ops vector.
82 	 */
83 	for (t = tdb_list; t != NULL; t = t->tdb_next) {
84 		if (strcmp(path, t->tdb_pathname) == 0)
85 			break;
86 	}
87 
88 	if (t != NULL)
89 		return (&t->tdb_ops);
90 
91 	/*
92 	 * Otherwise dlmopen the new library, look up its td_init() function,
93 	 * and call it.  If any of this fails, we return NULL for failure.
94 	 */
95 	if (access(path, F_OK) == -1)
96 		return (NULL);
97 
98 	if ((hdl = dlmopen(LM_ID_BASE, path, RTLD_LAZY | RTLD_LOCAL)) == NULL) {
99 		(void) set_errno(EMDB_RTLD);
100 		return (NULL);
101 	}
102 
103 	if ((tdb_init = (td_err_e (*)(void))dlsym(hdl, "td_init")) == NULL) {
104 		(void) dlclose(hdl);
105 		(void) set_errno(tdb_to_errno(TD_NOCAPAB));
106 		return (NULL);
107 	}
108 
109 	if ((err = tdb_init()) != TD_OK) {
110 		(void) dlclose(hdl);
111 		(void) set_errno(tdb_to_errno(err));
112 		return (NULL);
113 	}
114 
115 	/*
116 	 * If td_init() succeeds, we can't fail from here on.  Allocate a new
117 	 * library entry and add it to our linked list.
118 	 */
119 	t = mdb_alloc(sizeof (mdb_tdb_lib_t), UM_SLEEP);
120 
121 	(void) strncpy(t->tdb_pathname, path, MAXPATHLEN);
122 	t->tdb_pathname[MAXPATHLEN - 1] = '\0';
123 	t->tdb_handle = hdl;
124 	t->tdb_next = tdb_list;
125 	tdb_list = t;
126 
127 	/*
128 	 * For each function we need to call in the thread_db library, look it
129 	 * up using dlsym().  If we find it, add it to the ops vector.  If not,
130 	 * put the address of our default function (see above) in that slot.
131 	 */
132 
133 	t->tdb_ops.td_ta_new = (td_err_e (*)())dlsym(hdl, "td_ta_new");
134 	if (t->tdb_ops.td_ta_new == NULL)
135 		t->tdb_ops.td_ta_new = (td_err_e (*)())tdb_notsup;
136 
137 	t->tdb_ops.td_ta_delete = (td_err_e (*)())dlsym(hdl, "td_ta_delete");
138 	if (t->tdb_ops.td_ta_delete == NULL)
139 		t->tdb_ops.td_ta_delete = (td_err_e (*)())tdb_notsup;
140 
141 	t->tdb_ops.td_ta_thr_iter = (td_err_e (*)())
142 	    dlsym(hdl, "td_ta_thr_iter");
143 	if (t->tdb_ops.td_ta_thr_iter == NULL)
144 		t->tdb_ops.td_ta_thr_iter = (td_err_e (*)())tdb_notsup;
145 
146 	t->tdb_ops.td_ta_map_id2thr = (td_err_e (*)())
147 	    dlsym(hdl, "td_ta_map_id2thr");
148 	if (t->tdb_ops.td_ta_map_id2thr == NULL)
149 		t->tdb_ops.td_ta_map_id2thr = (td_err_e (*)())tdb_notsup;
150 
151 	t->tdb_ops.td_ta_map_lwp2thr = (td_err_e (*)())
152 	    dlsym(hdl, "td_ta_map_lwp2thr");
153 	if (t->tdb_ops.td_ta_map_lwp2thr == NULL)
154 		t->tdb_ops.td_ta_map_lwp2thr = (td_err_e (*)())tdb_notsup;
155 
156 	t->tdb_ops.td_thr_get_info = (td_err_e (*)())
157 	    dlsym(hdl, "td_thr_get_info");
158 	if (t->tdb_ops.td_thr_get_info == NULL)
159 		t->tdb_ops.td_thr_get_info = (td_err_e (*)())tdb_notsup;
160 
161 	t->tdb_ops.td_thr_getgregs = (td_err_e (*)())
162 	    dlsym(hdl, "td_thr_getgregs");
163 	if (t->tdb_ops.td_thr_getgregs == NULL)
164 		t->tdb_ops.td_thr_getgregs = (td_err_e (*)())tdb_notsup;
165 
166 	t->tdb_ops.td_thr_setgregs = (td_err_e (*)())
167 	    dlsym(hdl, "td_thr_setgregs");
168 	if (t->tdb_ops.td_thr_setgregs == NULL)
169 		t->tdb_ops.td_thr_setgregs = (td_err_e (*)())tdb_notsup;
170 
171 	t->tdb_ops.td_thr_getfpregs = (td_err_e (*)())
172 	    dlsym(hdl, "td_thr_getfpregs");
173 	if (t->tdb_ops.td_thr_getfpregs == NULL)
174 		t->tdb_ops.td_thr_getfpregs = (td_err_e (*)())tdb_notsup;
175 
176 	t->tdb_ops.td_thr_setfpregs = (td_err_e (*)())
177 	    dlsym(hdl, "td_thr_setfpregs");
178 	if (t->tdb_ops.td_thr_setfpregs == NULL)
179 		t->tdb_ops.td_thr_setfpregs = (td_err_e (*)())tdb_notsup;
180 
181 	t->tdb_ops.td_thr_tlsbase = (td_err_e (*)())
182 	    dlsym(hdl, "td_thr_tlsbase");
183 	if (t->tdb_ops.td_thr_tlsbase == NULL)
184 		t->tdb_ops.td_thr_tlsbase = (td_err_e (*)())tdb_notsup;
185 
186 	t->tdb_ops.td_thr_getxregsize = (td_err_e (*)())
187 	    dlsym(hdl, "td_thr_getxregsize");
188 	if (t->tdb_ops.td_thr_getxregsize == NULL)
189 		t->tdb_ops.td_thr_getxregsize = (td_err_e (*)())tdb_notsup;
190 
191 	t->tdb_ops.td_thr_getxregs = (td_err_e (*)())
192 	    dlsym(hdl, "td_thr_getxregs");
193 	if (t->tdb_ops.td_thr_getxregs == NULL)
194 		t->tdb_ops.td_thr_getxregs = (td_err_e (*)())tdb_notsup;
195 
196 	t->tdb_ops.td_thr_setxregs = (td_err_e (*)())
197 	    dlsym(hdl, "td_thr_setxregs");
198 	if (t->tdb_ops.td_thr_setxregs == NULL)
199 		t->tdb_ops.td_thr_setxregs = (td_err_e (*)())tdb_notsup;
200 
201 	return (&t->tdb_ops);
202 }
203 
204 void
mdb_tdb_flush(void)205 mdb_tdb_flush(void)
206 {
207 	mdb_tdb_lib_t *t, *u;
208 
209 	for (t = tdb_list; t != NULL; t = u) {
210 		u = t->tdb_next;
211 		(void) dlclose(t->tdb_handle);
212 		mdb_free(t, sizeof (mdb_tdb_lib_t));
213 	}
214 
215 	tdb_list = NULL;
216 }
217