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