xref: /illumos-gate/usr/src/cmd/mdb/common/mdb/mdb_module.c (revision 4a1c24318fe7c9bdae38ce58a2e4624597d297e2)
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 (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #include <sys/param.h>
27 #include <unistd.h>
28 #include <strings.h>
29 #include <dlfcn.h>
30 #include <link.h>
31 
32 #include <mdb/mdb_module.h>
33 #include <mdb/mdb_modapi.h>
34 #include <mdb/mdb_ctf.h>
35 #include <mdb/mdb_debug.h>
36 #include <mdb/mdb_callb.h>
37 #include <mdb/mdb_string.h>
38 #include <mdb/mdb_ks.h>
39 #include <mdb/mdb_err.h>
40 #include <mdb/mdb_io.h>
41 #include <mdb/mdb_frame.h>
42 #include <mdb/mdb_whatis_impl.h>
43 #include <mdb/mdb.h>
44 
45 /*
46  * For builtin modules, we set mod_init to this function, which just
47  * returns a constant modinfo struct with no dcmds and walkers.
48  */
49 static const mdb_modinfo_t *
50 builtin_init(void)
51 {
52 	static const mdb_modinfo_t info = { MDB_API_VERSION };
53 	return (&info);
54 }
55 
56 int
57 mdb_module_validate_name(const char *name, const char **errmsgp)
58 {
59 	if (strlen(name) == 0) {
60 		*errmsgp = "no module name was specified\n";
61 		return (0);
62 	}
63 
64 	if (strlen(name) > MDB_NV_NAMELEN) {
65 		*errmsgp = "module name '%s' exceeds name length limit\n";
66 		return (0);
67 	}
68 
69 	if (strbadid(name) != NULL) {
70 		*errmsgp = "module name '%s' contains illegal characters\n";
71 		return (0);
72 	}
73 
74 	if (mdb_nv_lookup(&mdb.m_modules, name) != NULL) {
75 		*errmsgp = "%s module is already loaded\n";
76 		return (0);
77 	}
78 
79 	return (1);
80 }
81 
82 int
83 mdb_module_create(const char *name, const char *fname, int mode,
84     mdb_module_t **mpp)
85 {
86 	static const mdb_walker_t empty_walk_list[] = { 0 };
87 	static const mdb_dcmd_t empty_dcmd_list[] = { 0 };
88 
89 	int dlmode = (mode & MDB_MOD_GLOBAL) ? RTLD_GLOBAL : RTLD_LOCAL;
90 
91 	const mdb_modinfo_t *info;
92 	const mdb_dcmd_t *dcp;
93 	const mdb_walker_t *wp;
94 
95 	mdb_module_t *mod;
96 
97 	mod = mdb_zalloc(sizeof (mdb_module_t), UM_SLEEP);
98 	mod->mod_info = mdb_alloc(sizeof (mdb_modinfo_t), UM_SLEEP);
99 
100 	(void) mdb_nv_create(&mod->mod_dcmds, UM_SLEEP);
101 	(void) mdb_nv_create(&mod->mod_walkers, UM_SLEEP);
102 
103 	mod->mod_name = strdup(name);
104 	mdb.m_lmod = mod;		/* Mark module as currently loading */
105 
106 	if (!(mode & MDB_MOD_BUILTIN)) {
107 		mdb_dprintf(MDB_DBG_MODULE, "dlopen %s %x\n", fname, dlmode);
108 		mod->mod_hdl = dlmopen(LM_ID_BASE, fname, RTLD_NOW | dlmode);
109 
110 		if (mod->mod_hdl == NULL) {
111 			warn("%s\n", dlerror());
112 			goto err;
113 		}
114 
115 		mod->mod_init = (const mdb_modinfo_t *(*)(void))
116 		    dlsym(mod->mod_hdl, "_mdb_init");
117 
118 		mod->mod_fini = (void (*)(void))
119 		    dlsym(mod->mod_hdl, "_mdb_fini");
120 
121 		mod->mod_tgt_ctor = (mdb_tgt_ctor_f *)
122 		    dlsym(mod->mod_hdl, "_mdb_tgt_create");
123 
124 		mod->mod_dis_ctor = (mdb_dis_ctor_f *)
125 		    dlsym(mod->mod_hdl, "_mdb_dis_create");
126 
127 		if (!(mdb.m_flags & MDB_FL_NOCTF))
128 			mod->mod_ctfp = mdb_ctf_open(fname, NULL);
129 	} else {
130 #ifdef _KMDB
131 		/*
132 		 * mdb_ks is a special case - a builtin with _mdb_init and
133 		 * _mdb_fini routines.  If we don't hack it in here, we'll have
134 		 * to duplicate most of the module creation code elsewhere.
135 		 */
136 		if (strcmp(name, "mdb_ks") == 0)
137 			mod->mod_init = mdb_ks_init;
138 		else
139 #endif
140 			mod->mod_init = builtin_init;
141 	}
142 
143 	if (mod->mod_init == NULL) {
144 		warn("%s module is missing _mdb_init definition\n", name);
145 		goto err;
146 	}
147 
148 	if ((info = mod->mod_init()) == NULL) {
149 		warn("%s module failed to initialize\n", name);
150 		goto err;
151 	}
152 
153 	/*
154 	 * Reject modules compiled for a newer version of the debugger.
155 	 */
156 	if (info->mi_dvers > MDB_API_VERSION) {
157 		warn("%s module requires newer mdb API version (%hu) than "
158 		    "debugger (%d)\n", name, info->mi_dvers, MDB_API_VERSION);
159 		goto err;
160 	}
161 
162 	/*
163 	 * Load modules compiled for the current API version.
164 	 */
165 	switch (info->mi_dvers) {
166 	case MDB_API_VERSION:
167 	case 2:
168 	case 1:
169 		/*
170 		 * Current API version -- copy entire modinfo
171 		 * structure into our own private storage.
172 		 */
173 		bcopy(info, mod->mod_info, sizeof (mdb_modinfo_t));
174 		if (mod->mod_info->mi_dcmds == NULL)
175 			mod->mod_info->mi_dcmds = empty_dcmd_list;
176 		if (mod->mod_info->mi_walkers == NULL)
177 			mod->mod_info->mi_walkers = empty_walk_list;
178 		break;
179 	default:
180 		/*
181 		 * Too old to be compatible -- abort the load.
182 		 */
183 		warn("%s module is compiled for obsolete mdb API "
184 		    "version %hu\n", name, info->mi_dvers);
185 		goto err;
186 	}
187 
188 	/*
189 	 * Before we actually go ahead with the load, we need to check
190 	 * each dcmd and walk structure for any invalid values:
191 	 */
192 	for (dcp = &mod->mod_info->mi_dcmds[0]; dcp->dc_name != NULL; dcp++) {
193 		if (strbadid(dcp->dc_name) != NULL) {
194 			warn("dcmd name '%s' contains illegal characters\n",
195 			    dcp->dc_name);
196 			goto err;
197 		}
198 
199 		if (dcp->dc_descr == NULL) {
200 			warn("dcmd '%s' must have a description\n",
201 			    dcp->dc_name);
202 			goto err;
203 		}
204 
205 		if (dcp->dc_funcp == NULL) {
206 			warn("dcmd '%s' has a NULL function pointer\n",
207 			    dcp->dc_name);
208 			goto err;
209 		}
210 	}
211 
212 	for (wp = &mod->mod_info->mi_walkers[0]; wp->walk_name != NULL; wp++) {
213 		if (strbadid(wp->walk_name) != NULL) {
214 			warn("walk name '%s' contains illegal characters\n",
215 			    wp->walk_name);
216 			goto err;
217 		}
218 
219 		if (wp->walk_descr == NULL) {
220 			warn("walk '%s' must have a description\n",
221 			    wp->walk_name);
222 			goto err;
223 		}
224 
225 		if (wp->walk_step == NULL) {
226 			warn("walk '%s' has a NULL walk_step function\n",
227 			    wp->walk_name);
228 			goto err;
229 		}
230 	}
231 
232 	/*
233 	 * Now that we've established that there are no problems,
234 	 * we can go ahead and hash the module, and its dcmds and walks:
235 	 */
236 	(void) mdb_nv_insert(&mdb.m_modules, mod->mod_name, NULL,
237 	    (uintptr_t)mod, MDB_NV_RDONLY|MDB_NV_EXTNAME);
238 
239 	for (dcp = &mod->mod_info->mi_dcmds[0]; dcp->dc_name != NULL; dcp++) {
240 		if (mdb_module_add_dcmd(mod, dcp, mode) == -1)
241 			warn("failed to load dcmd %s`%s", name, dcp->dc_name);
242 	}
243 
244 	for (wp = &mod->mod_info->mi_walkers[0]; wp->walk_name != NULL; wp++) {
245 		if (mdb_module_add_walker(mod, wp, mode) == -1)
246 			warn("failed to load walk %s`%s", name, wp->walk_name);
247 	}
248 
249 	/*
250 	 * Add the module to the end of the list of modules in load-dependency
251 	 * order.  We maintain this list so we can unload in reverse order.
252 	 */
253 	if (mdb.m_mtail != NULL) {
254 		ASSERT(mdb.m_mtail->mod_next == NULL);
255 		mdb.m_mtail->mod_next = mod;
256 		mod->mod_prev = mdb.m_mtail;
257 		mdb.m_mtail = mod;
258 	} else {
259 		ASSERT(mdb.m_mhead == NULL);
260 		mdb.m_mtail = mdb.m_mhead = mod;
261 	}
262 
263 	mdb.m_lmod = NULL;
264 	if (mpp != NULL)
265 		*mpp = mod;
266 	return (0);
267 
268 err:
269 	mdb_whatis_unregister_module(mod);
270 
271 	if (mod->mod_ctfp != NULL)
272 		ctf_close(mod->mod_ctfp);
273 
274 	if (mod->mod_hdl != NULL)
275 		(void) dlclose(mod->mod_hdl);
276 
277 	mdb_nv_destroy(&mod->mod_dcmds);
278 	mdb_nv_destroy(&mod->mod_walkers);
279 
280 	strfree((char *)mod->mod_name);
281 	mdb_free(mod->mod_info, sizeof (mdb_modinfo_t));
282 	mdb_free(mod, sizeof (mdb_module_t));
283 
284 	mdb.m_lmod = NULL;
285 	return (-1);
286 }
287 
288 mdb_module_t *
289 mdb_module_load_builtin(const char *name)
290 {
291 	mdb_module_t *mp;
292 
293 	if (mdb_module_create(name, NULL, MDB_MOD_BUILTIN, &mp) < 0)
294 		return (NULL);
295 	return (mp);
296 }
297 
298 int
299 mdb_module_unload_common(const char *name)
300 {
301 	mdb_var_t *v = mdb_nv_lookup(&mdb.m_modules, name);
302 	mdb_module_t *mod;
303 
304 	if (v == NULL)
305 		return (set_errno(EMDB_NOMOD));
306 
307 	mod = mdb_nv_get_cookie(v);
308 
309 	if (mod == &mdb.m_rmod || mod->mod_hdl == NULL)
310 		return (set_errno(EMDB_BUILTINMOD));
311 
312 	mdb_dprintf(MDB_DBG_MODULE, "unloading %s\n", name);
313 
314 	if (mod->mod_fini != NULL) {
315 		mdb_dprintf(MDB_DBG_MODULE, "calling %s`_mdb_fini\n", name);
316 		mod->mod_fini();
317 	}
318 
319 	mdb_whatis_unregister_module(mod);
320 
321 	if (mod->mod_ctfp != NULL)
322 		ctf_close(mod->mod_ctfp);
323 
324 	if (mod->mod_cb != NULL)
325 		mdb_callb_remove_by_mod(mod);
326 
327 	if (mod->mod_prev == NULL) {
328 		ASSERT(mdb.m_mhead == mod);
329 		mdb.m_mhead = mod->mod_next;
330 	} else
331 		mod->mod_prev->mod_next = mod->mod_next;
332 
333 	if (mod->mod_next == NULL) {
334 		ASSERT(mdb.m_mtail == mod);
335 		mdb.m_mtail = mod->mod_prev;
336 	} else
337 		mod->mod_next->mod_prev = mod->mod_prev;
338 
339 	while (mdb_nv_size(&mod->mod_walkers) != 0) {
340 		mdb_nv_rewind(&mod->mod_walkers);
341 		v = mdb_nv_peek(&mod->mod_walkers);
342 		(void) mdb_module_remove_walker(mod, mdb_nv_get_name(v));
343 	}
344 
345 	while (mdb_nv_size(&mod->mod_dcmds) != 0) {
346 		mdb_nv_rewind(&mod->mod_dcmds);
347 		v = mdb_nv_peek(&mod->mod_dcmds);
348 		(void) mdb_module_remove_dcmd(mod, mdb_nv_get_name(v));
349 	}
350 
351 	v = mdb_nv_lookup(&mdb.m_modules, name);
352 	ASSERT(v != NULL);
353 	mdb_nv_remove(&mdb.m_modules, v);
354 
355 	(void) dlclose(mod->mod_hdl);
356 
357 	mdb_nv_destroy(&mod->mod_walkers);
358 	mdb_nv_destroy(&mod->mod_dcmds);
359 
360 	strfree((char *)mod->mod_name);
361 	mdb_free(mod->mod_info, sizeof (mdb_modinfo_t));
362 	mdb_free(mod, sizeof (mdb_module_t));
363 
364 	return (0);
365 }
366 
367 int
368 mdb_module_add_dcmd(mdb_module_t *mod, const mdb_dcmd_t *dcp, int flags)
369 {
370 	mdb_var_t *v = mdb_nv_lookup(&mod->mod_dcmds, dcp->dc_name);
371 	mdb_idcmd_t *idcp;
372 
373 	uint_t nflag = MDB_NV_OVERLOAD | MDB_NV_SILENT;
374 
375 	if (flags & MDB_MOD_FORCE)
376 		nflag |= MDB_NV_INTERPOS;
377 
378 	if (v != NULL)
379 		return (set_errno(EMDB_DCMDEXISTS));
380 
381 	idcp = mdb_alloc(sizeof (mdb_idcmd_t), UM_SLEEP);
382 
383 	idcp->idc_usage = dcp->dc_usage;
384 	idcp->idc_descr = dcp->dc_descr;
385 	idcp->idc_help = dcp->dc_help;
386 	idcp->idc_funcp = dcp->dc_funcp;
387 	idcp->idc_modp = mod;
388 
389 	v = mdb_nv_insert(&mod->mod_dcmds, dcp->dc_name, NULL,
390 	    (uintptr_t)idcp, MDB_NV_SILENT | MDB_NV_RDONLY);
391 
392 	idcp->idc_name = mdb_nv_get_name(v);
393 	idcp->idc_var = mdb_nv_insert(&mdb.m_dcmds, idcp->idc_name, NULL,
394 	    (uintptr_t)v, nflag);
395 
396 	mdb_dprintf(MDB_DBG_DCMD, "added dcmd %s`%s\n",
397 	    mod->mod_name, idcp->idc_name);
398 
399 	return (0);
400 }
401 
402 int
403 mdb_module_remove_dcmd(mdb_module_t *mod, const char *dname)
404 {
405 	mdb_var_t *v = mdb_nv_lookup(&mod->mod_dcmds, dname);
406 	mdb_idcmd_t *idcp;
407 	mdb_cmd_t *cp;
408 
409 	if (v == NULL)
410 		return (set_errno(EMDB_NODCMD));
411 
412 	mdb_dprintf(MDB_DBG_DCMD, "removed dcmd %s`%s\n", mod->mod_name, dname);
413 	idcp = mdb_nv_get_cookie(v);
414 
415 	/*
416 	 * If we're removing a dcmd that is part of the most recent command,
417 	 * we need to free mdb.m_lastcp so we don't attempt to execute some
418 	 * text we've removed from our address space if -o repeatlast is set.
419 	 */
420 	for (cp = mdb_list_next(&mdb.m_lastc); cp; cp = mdb_list_next(cp)) {
421 		if (cp->c_dcmd == idcp) {
422 			while ((cp = mdb_list_next(&mdb.m_lastc)) != NULL) {
423 				mdb_list_delete(&mdb.m_lastc, cp);
424 				mdb_cmd_destroy(cp);
425 			}
426 			break;
427 		}
428 	}
429 
430 	mdb_nv_remove(&mdb.m_dcmds, idcp->idc_var);
431 	mdb_nv_remove(&mod->mod_dcmds, v);
432 	mdb_free(idcp, sizeof (mdb_idcmd_t));
433 
434 	return (0);
435 }
436 
437 /*ARGSUSED*/
438 static int
439 default_walk_init(mdb_walk_state_t *wsp)
440 {
441 	return (WALK_NEXT);
442 }
443 
444 /*ARGSUSED*/
445 static void
446 default_walk_fini(mdb_walk_state_t *wsp)
447 {
448 	/* Nothing to do here */
449 }
450 
451 int
452 mdb_module_add_walker(mdb_module_t *mod, const mdb_walker_t *wp, int flags)
453 {
454 	mdb_var_t *v = mdb_nv_lookup(&mod->mod_walkers, wp->walk_name);
455 	mdb_iwalker_t *iwp;
456 
457 	uint_t nflag = MDB_NV_OVERLOAD | MDB_NV_SILENT;
458 
459 	if (flags & MDB_MOD_FORCE)
460 		nflag |= MDB_NV_INTERPOS;
461 
462 	if (v != NULL)
463 		return (set_errno(EMDB_WALKEXISTS));
464 
465 	if (wp->walk_descr == NULL || wp->walk_step == NULL)
466 		return (set_errno(EINVAL));
467 
468 	iwp = mdb_alloc(sizeof (mdb_iwalker_t), UM_SLEEP);
469 
470 	iwp->iwlk_descr = strdup(wp->walk_descr);
471 	iwp->iwlk_init = wp->walk_init;
472 	iwp->iwlk_step = wp->walk_step;
473 	iwp->iwlk_fini = wp->walk_fini;
474 	iwp->iwlk_init_arg = wp->walk_init_arg;
475 	iwp->iwlk_modp = mod;
476 
477 	if (iwp->iwlk_init == NULL)
478 		iwp->iwlk_init = default_walk_init;
479 	if (iwp->iwlk_fini == NULL)
480 		iwp->iwlk_fini = default_walk_fini;
481 
482 	v = mdb_nv_insert(&mod->mod_walkers, wp->walk_name, NULL,
483 	    (uintptr_t)iwp, MDB_NV_SILENT | MDB_NV_RDONLY);
484 
485 	iwp->iwlk_name = mdb_nv_get_name(v);
486 	iwp->iwlk_var = mdb_nv_insert(&mdb.m_walkers, iwp->iwlk_name, NULL,
487 	    (uintptr_t)v, nflag);
488 
489 	mdb_dprintf(MDB_DBG_WALK, "added walk %s`%s\n",
490 	    mod->mod_name, iwp->iwlk_name);
491 
492 	return (0);
493 }
494 
495 int
496 mdb_module_remove_walker(mdb_module_t *mod, const char *wname)
497 {
498 	mdb_var_t *v = mdb_nv_lookup(&mod->mod_walkers, wname);
499 	mdb_iwalker_t *iwp;
500 
501 	if (v == NULL)
502 		return (set_errno(EMDB_NOWALK));
503 
504 	mdb_dprintf(MDB_DBG_WALK, "removed walk %s`%s\n", mod->mod_name, wname);
505 
506 	iwp = mdb_nv_get_cookie(v);
507 	mdb_nv_remove(&mdb.m_walkers, iwp->iwlk_var);
508 	mdb_nv_remove(&mod->mod_walkers, v);
509 
510 	strfree(iwp->iwlk_descr);
511 	mdb_free(iwp, sizeof (mdb_iwalker_t));
512 
513 	return (0);
514 }
515 
516 void
517 mdb_module_unload_all(int mode)
518 {
519 	mdb_module_t *mod, *pmod;
520 
521 	/*
522 	 * We unload modules in the reverse order in which they were loaded
523 	 * so as to allow _mdb_fini routines to invoke code which may be
524 	 * present in a previously-loaded module (such as mdb_ks, etc.).
525 	 */
526 	for (mod = mdb.m_mtail; mod != NULL; mod = pmod) {
527 		pmod =  mod->mod_prev;
528 		(void) mdb_module_unload(mod->mod_name, mode);
529 	}
530 }
531