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