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 /*
27  * Copyright (c) 2012 by Delphix. All rights reserved.
28  * Copyright (c) 2012 Joyent, Inc. All rights reserved.
29  */
30 
31 #include <mdb/mdb_modapi.h>
32 #include <mdb/mdb.h>
33 #include <mdb/mdb_io.h>
34 #include <mdb/mdb_module.h>
35 #include <mdb/mdb_string.h>
36 #include <mdb/mdb_whatis.h>
37 #include <mdb/mdb_whatis_impl.h>
38 #include <limits.h>
39 
40 static int whatis_debug = 0;
41 
42 /* for bsearch;  r is an array of {base, size}, e points into w->w_addrs */
43 static int
find_range(const void * r,const void * e)44 find_range(const void *r, const void *e)
45 {
46 	const uintptr_t *range = r;
47 	uintptr_t el = *(const uintptr_t *)e;
48 
49 	if (el < range[0])
50 		return (1);
51 
52 	if ((el - range[0]) >= range[1])
53 		return (-1);
54 
55 	return (0);
56 }
57 
58 /* for qsort; simple uintptr comparator */
59 static int
uintptr_cmp(const void * l,const void * r)60 uintptr_cmp(const void *l, const void *r)
61 {
62 	uintptr_t lhs = *(const uintptr_t *)l;
63 	uintptr_t rhs = *(const uintptr_t *)r;
64 
65 	if (lhs < rhs)
66 		return (-1);
67 	if (lhs > rhs)
68 		return (1);
69 	return (0);
70 }
71 
72 static const uintptr_t *
mdb_whatis_search(mdb_whatis_t * w,uintptr_t base,size_t size)73 mdb_whatis_search(mdb_whatis_t *w, uintptr_t base, size_t size)
74 {
75 	uintptr_t range[2];
76 
77 	range[0] = base;
78 	range[1] = size;
79 
80 	return (bsearch(range, w->w_addrs, w->w_naddrs, sizeof (*w->w_addrs),
81 	    find_range));
82 }
83 
84 /*
85  * Returns non-zero if and only if there is at least one address of interest
86  * in the range [base, base+size).
87  */
88 int
mdb_whatis_overlaps(mdb_whatis_t * w,uintptr_t base,size_t size)89 mdb_whatis_overlaps(mdb_whatis_t *w, uintptr_t base, size_t size)
90 {
91 	const uintptr_t *f;
92 	uint_t offset, cur;
93 
94 	if (whatis_debug && w->w_magic != WHATIS_MAGIC) {
95 		mdb_warn(
96 		    "mdb_whatis_overlaps(): bogus mdb_whatis_t pointer\n");
97 		return (0);
98 	}
99 
100 	if (w->w_done || size == 0)
101 		return (0);
102 
103 	if (base + size - 1 < base) {
104 		mdb_warn("mdb_whatis_overlaps(): [%p, %p+%p) overflows\n",
105 		    base, base, size);
106 		return (0);
107 	}
108 
109 	f = mdb_whatis_search(w, base, size);
110 	if (f == NULL)
111 		return (0);
112 
113 	cur = offset = f - w->w_addrs;
114 
115 	/*
116 	 * We only return success if there's an address we'll actually
117 	 * match in the range.  We can quickly check for the ALL flag
118 	 * or a non-found address at our match point.
119 	 */
120 	if ((w->w_flags & WHATIS_ALL) || !w->w_addrfound[cur])
121 		return (1);
122 
123 	/* Search backwards then forwards for a non-found address */
124 	while (cur > 0) {
125 		cur--;
126 
127 		if (w->w_addrs[cur] < base)
128 			break;
129 
130 		if (!w->w_addrfound[cur])
131 			return (1);
132 	}
133 
134 	for (cur = offset + 1; cur < w->w_naddrs; cur++) {
135 		if ((w->w_addrs[cur] - base) >= size)
136 			break;
137 
138 		if (!w->w_addrfound[cur])
139 			return (1);
140 	}
141 
142 	return (0);			/* everything has already been seen */
143 }
144 
145 /*
146  * Iteratively search our list of addresses for matches in [base, base+size).
147  */
148 int
mdb_whatis_match(mdb_whatis_t * w,uintptr_t base,size_t size,uintptr_t * out)149 mdb_whatis_match(mdb_whatis_t *w, uintptr_t base, size_t size, uintptr_t *out)
150 {
151 	size_t offset;
152 
153 	if (whatis_debug) {
154 		if (w->w_magic != WHATIS_MAGIC) {
155 			mdb_warn(
156 			    "mdb_whatis_match(): bogus mdb_whatis_t pointer\n");
157 			goto done;
158 		}
159 	}
160 
161 	if (w->w_done || size == 0)
162 		goto done;
163 
164 	if (base + size - 1 < base) {
165 		mdb_warn("mdb_whatis_match(): [%p, %p+%x) overflows\n",
166 		    base, base, size);
167 		return (0);
168 	}
169 
170 	if ((offset = w->w_match_next) != 0 &&
171 	    (base != w->w_match_base || size != w->w_match_size)) {
172 		mdb_warn("mdb_whatis_match(): new range [%p, %p+%p) "
173 		    "while still searching [%p, %p+%p)\n",
174 		    base, base, size,
175 		    w->w_match_base, w->w_match_base, w->w_match_size);
176 		offset = 0;
177 	}
178 
179 	if (offset == 0) {
180 		const uintptr_t *f = mdb_whatis_search(w, base, size);
181 
182 		if (f == NULL)
183 			goto done;
184 
185 		offset = (f - w->w_addrs);
186 
187 		/* Walk backwards until we reach the first match */
188 		while (offset > 0 && w->w_addrs[offset - 1] >= base)
189 			offset--;
190 
191 		w->w_match_base = base;
192 		w->w_match_size = size;
193 	}
194 
195 	for (; offset < w->w_naddrs && ((w->w_addrs[offset] - base) < size);
196 	    offset++) {
197 
198 		*out = w->w_addrs[offset];
199 		w->w_match_next = offset + 1;
200 
201 		if (w->w_addrfound[offset]) {
202 			/* if we're not seeing everything, skip it */
203 			if (!(w->w_flags & WHATIS_ALL))
204 				continue;
205 
206 			return (1);
207 		}
208 
209 		/* We haven't seen this address yet. */
210 		w->w_found++;
211 		w->w_addrfound[offset] = 1;
212 
213 		/* If we've found them all, we're done */
214 		if (w->w_found == w->w_naddrs && !(w->w_flags & WHATIS_ALL))
215 			w->w_done = 1;
216 
217 		return (1);
218 	}
219 
220 done:
221 	w->w_match_next = 0;
222 	w->w_match_base = 0;
223 	w->w_match_size = 0;
224 	return (0);
225 }
226 
227 /*
228  * Report a pointer (addr) in an object beginning at (base) in standard
229  * whatis-style.  (format, ...) are mdb_printf() arguments, to be printed
230  * after the address information.  The caller is responsible for printing
231  * a newline (either in format or after the call returns)
232  */
233 /*ARGSUSED*/
234 void
mdb_whatis_report_object(mdb_whatis_t * w,uintptr_t addr,uintptr_t base,const char * format,...)235 mdb_whatis_report_object(mdb_whatis_t *w,
236     uintptr_t addr, uintptr_t base, const char *format, ...)
237 {
238 	va_list alist;
239 
240 	if (whatis_debug) {
241 		if (mdb_whatis_search(w, addr, 1) == NULL)
242 			mdb_warn("mdb_whatis_report_object(): addr "
243 			    "%p is not a pointer of interest.\n", addr);
244 	}
245 
246 	if (addr < base)
247 		mdb_warn("whatis: addr (%p) is less than base (%p)\n",
248 		    addr, base);
249 
250 	if (addr == base)
251 		mdb_printf("%p is ", addr);
252 	else
253 		mdb_printf("%p is %p+%p, ", addr, base, addr - base);
254 
255 	if (format == NULL)
256 		return;
257 
258 	va_start(alist, format);
259 	mdb_iob_vprintf(mdb.m_out, format, alist);
260 	va_end(alist);
261 }
262 
263 /*
264  * Report an address (addr), with symbolic information if available, in
265  * standard whatis-style.  (format, ...) are mdb_printf() arguments, to be
266  * printed after the address information.  The caller is responsible for
267  * printing a newline (either in format or after the call returns)
268  */
269 /*ARGSUSED*/
270 void
mdb_whatis_report_address(mdb_whatis_t * w,uintptr_t addr,const char * format,...)271 mdb_whatis_report_address(mdb_whatis_t *w, uintptr_t addr,
272     const char *format, ...)
273 {
274 	GElf_Sym sym;
275 	va_list alist;
276 
277 	if (whatis_debug) {
278 		if (mdb_whatis_search(w, addr, 1) == NULL)
279 			mdb_warn("mdb_whatis_report_adddress(): addr "
280 			    "%p is not a pointer of interest.\n", addr);
281 	}
282 
283 	mdb_printf("%p is ", addr);
284 
285 	if (mdb_lookup_by_addr(addr, MDB_SYM_FUZZY, NULL, 0, &sym) != -1 &&
286 	    (addr - (uintptr_t)sym.st_value) < sym.st_size) {
287 		mdb_printf("%a, ", addr);
288 	}
289 
290 	va_start(alist, format);
291 	mdb_iob_vprintf(mdb.m_out, format, alist);
292 	va_end(alist);
293 }
294 
295 uint_t
mdb_whatis_flags(mdb_whatis_t * w)296 mdb_whatis_flags(mdb_whatis_t *w)
297 {
298 	/* Mask out the internal-only flags */
299 	return (w->w_flags & WHATIS_PUBLIC);
300 }
301 
302 uint_t
mdb_whatis_done(mdb_whatis_t * w)303 mdb_whatis_done(mdb_whatis_t *w)
304 {
305 	return (w->w_done);
306 }
307 
308 /*
309  * Whatis callback list management
310  */
311 typedef struct whatis_callback {
312 	uint64_t	wcb_index;
313 	mdb_module_t	*wcb_module;
314 	const char	*wcb_modname;
315 	char		*wcb_name;
316 	mdb_whatis_cb_f	*wcb_func;
317 	void		*wcb_arg;
318 	uint_t		wcb_prio;
319 	uint_t		wcb_flags;
320 } whatis_callback_t;
321 
322 static whatis_callback_t builtin_whatis[] = {
323 	{ 0, NULL, "mdb", "mappings", whatis_run_mappings, NULL,
324 	    WHATIS_PRIO_MIN, WHATIS_REG_NO_ID }
325 };
326 #define	NBUILTINS	(sizeof (builtin_whatis) / sizeof (*builtin_whatis))
327 
328 static whatis_callback_t *whatis_cb_start[NBUILTINS];
329 static whatis_callback_t **whatis_cb = NULL;	/* callback array */
330 static size_t whatis_cb_count;			/* count of callbacks */
331 static size_t whatis_cb_size;			/* size of whatis_cb array */
332 static uint64_t whatis_cb_index;		/* global count */
333 
334 #define	WHATIS_CB_SIZE_MIN	8	/* initial allocation size */
335 
336 static int
whatis_cbcmp(const void * lhs,const void * rhs)337 whatis_cbcmp(const void *lhs, const void *rhs)
338 {
339 	whatis_callback_t *l = *(whatis_callback_t * const *)lhs;
340 	whatis_callback_t *r = *(whatis_callback_t * const *)rhs;
341 	int ret;
342 
343 	/* First, handle NULLs; we want them at the end */
344 	if (l == NULL && r == NULL)
345 		return (0);
346 	if (l == NULL)
347 		return (1);
348 	if (r == NULL)
349 		return (-1);
350 
351 	/* Next, compare priorities */
352 	if (l->wcb_prio < r->wcb_prio)
353 		return (-1);
354 	if (l->wcb_prio > r->wcb_prio)
355 		return (1);
356 
357 	/* then module name */
358 	if ((ret = strcmp(l->wcb_modname, r->wcb_modname)) != 0)
359 		return (ret);
360 
361 	/* and finally insertion order */
362 	if (l->wcb_index < r->wcb_index)
363 		return (-1);
364 	if (l->wcb_index > r->wcb_index)
365 		return (1);
366 
367 	mdb_warn("whatis_cbcmp(): can't happen: duplicate indices\n");
368 	return (0);
369 }
370 
371 static void
whatis_init(void)372 whatis_init(void)
373 {
374 	int idx;
375 
376 	for (idx = 0; idx < NBUILTINS; idx++) {
377 		whatis_cb_start[idx] = &builtin_whatis[idx];
378 		whatis_cb_start[idx]->wcb_index = idx;
379 	}
380 	whatis_cb_index = idx;
381 
382 	whatis_cb = whatis_cb_start;
383 	whatis_cb_count = whatis_cb_size = NBUILTINS;
384 
385 	qsort(whatis_cb, whatis_cb_count, sizeof (*whatis_cb), whatis_cbcmp);
386 }
387 
388 void
mdb_whatis_register(const char * name,mdb_whatis_cb_f * func,void * arg,uint_t prio,uint_t flags)389 mdb_whatis_register(const char *name, mdb_whatis_cb_f *func, void *arg,
390     uint_t prio, uint_t flags)
391 {
392 	whatis_callback_t *wcp;
393 
394 	if (mdb.m_lmod == NULL) {
395 		mdb_warn("mdb_whatis_register(): can only be called during "
396 		    "module load\n");
397 		return;
398 	}
399 
400 	if (strbadid(name)) {
401 		mdb_warn("mdb_whatis_register(): whatis name '%s' contains "
402 		    "illegal characters\n");
403 		return;
404 	}
405 
406 	if ((flags & ~(WHATIS_REG_NO_ID|WHATIS_REG_ID_ONLY)) != 0) {
407 		mdb_warn("mdb_whatis_register(): flags (%x) contain unknown "
408 		    "flags\n", flags);
409 		return;
410 	}
411 	if ((flags & WHATIS_REG_NO_ID) && (flags & WHATIS_REG_ID_ONLY)) {
412 		mdb_warn("mdb_whatis_register(): flags (%x) contains both "
413 		    "NO_ID and ID_ONLY.\n", flags);
414 		return;
415 	}
416 
417 	if (prio > WHATIS_PRIO_MIN)
418 		prio = WHATIS_PRIO_MIN;
419 
420 	if (whatis_cb == NULL)
421 		whatis_init();
422 
423 	wcp = mdb_zalloc(sizeof (*wcp), UM_SLEEP);
424 
425 	wcp->wcb_index = whatis_cb_index++;
426 	wcp->wcb_prio = prio;
427 	wcp->wcb_module = mdb.m_lmod;
428 	wcp->wcb_modname = mdb.m_lmod->mod_name;
429 	wcp->wcb_name = strdup(name);
430 	wcp->wcb_func = func;
431 	wcp->wcb_arg = arg;
432 	wcp->wcb_flags = flags;
433 
434 	/*
435 	 * See if we need to grow the array;  note that at initialization
436 	 * time, whatis_cb_count is greater than whatis_cb_size; this clues
437 	 * us in to the fact that the array doesn't need to be freed.
438 	 */
439 	if (whatis_cb_count == whatis_cb_size) {
440 		size_t nsize = MAX(2 * whatis_cb_size, WHATIS_CB_SIZE_MIN);
441 
442 		size_t obytes = sizeof (*whatis_cb) * whatis_cb_size;
443 		size_t nbytes = sizeof (*whatis_cb) * nsize;
444 
445 		whatis_callback_t **narray = mdb_zalloc(nbytes, UM_SLEEP);
446 
447 		bcopy(whatis_cb, narray, obytes);
448 
449 		if (whatis_cb != whatis_cb_start)
450 			mdb_free(whatis_cb, obytes);
451 		whatis_cb = narray;
452 		whatis_cb_size = nsize;
453 	}
454 
455 	/* add it into the table and re-sort */
456 	whatis_cb[whatis_cb_count++] = wcp;
457 	qsort(whatis_cb, whatis_cb_count, sizeof (*whatis_cb), whatis_cbcmp);
458 }
459 
460 void
mdb_whatis_unregister_module(mdb_module_t * mod)461 mdb_whatis_unregister_module(mdb_module_t *mod)
462 {
463 	int found = 0;
464 	int idx;
465 
466 	if (mod == NULL)
467 		return;
468 
469 	for (idx = 0; idx < whatis_cb_count; idx++) {
470 		whatis_callback_t *cur = whatis_cb[idx];
471 
472 		if (cur->wcb_module == mod) {
473 			found++;
474 			whatis_cb[idx] = NULL;
475 
476 			strfree(cur->wcb_name);
477 			mdb_free(cur, sizeof (*cur));
478 		}
479 	}
480 	/* If any were removed, compact the array */
481 	if (found != 0) {
482 		qsort(whatis_cb, whatis_cb_count, sizeof (*whatis_cb),
483 		    whatis_cbcmp);
484 		whatis_cb_count -= found;
485 	}
486 }
487 
488 int
cmd_whatis(uintptr_t addr,uint_t flags,int argc,const mdb_arg_t * argv)489 cmd_whatis(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
490 {
491 	mdb_whatis_t w;
492 	size_t idx;
493 	int ret;
494 	int keep = 0;
495 	int list = 0;
496 
497 	if (flags & DCMD_PIPE_OUT) {
498 		mdb_warn("whatis: cannot be output into a pipe\n");
499 		return (DCMD_ERR);
500 	}
501 
502 	if (mdb.m_lmod != NULL) {
503 		mdb_warn("whatis: cannot be called during module load\n");
504 		return (DCMD_ERR);
505 	}
506 
507 	if (whatis_cb == NULL)
508 		whatis_init();
509 
510 	bzero(&w, sizeof (w));
511 	w.w_magic = WHATIS_MAGIC;
512 
513 	whatis_debug = 0;
514 
515 	if (mdb_getopts(argc, argv,
516 	    'D', MDB_OPT_SETBITS, TRUE, &whatis_debug,		/* hidden */
517 	    'b', MDB_OPT_SETBITS, WHATIS_BUFCTL, &w.w_flags,	/* hidden */
518 	    'l', MDB_OPT_SETBITS, TRUE, &list,			/* hidden */
519 	    'a', MDB_OPT_SETBITS, WHATIS_ALL, &w.w_flags,
520 	    'i', MDB_OPT_SETBITS, WHATIS_IDSPACE, &w.w_flags,
521 	    'k', MDB_OPT_SETBITS, TRUE, &keep,
522 	    'q', MDB_OPT_SETBITS, WHATIS_QUIET, &w.w_flags,
523 	    'v', MDB_OPT_SETBITS, WHATIS_VERBOSE, &w.w_flags,
524 	    NULL) != argc)
525 		return (DCMD_USAGE);
526 
527 	if (list) {
528 		mdb_printf("%<u>%-16s %-12s %4s %?s %?s %8s%</u>",
529 		    "NAME", "MODULE", "PRIO", "FUNC", "ARG", "FLAGS");
530 
531 		for (idx = 0; idx < whatis_cb_count; idx++) {
532 			whatis_callback_t *cur = whatis_cb[idx];
533 
534 			const char *curfl =
535 			    (cur->wcb_flags & WHATIS_REG_NO_ID) ? "NO_ID" :
536 			    (cur->wcb_flags & WHATIS_REG_ID_ONLY) ? "ID_ONLY" :
537 			    "none";
538 
539 			mdb_printf("%-16s %-12s %4d %-?p %-?p %8s\n",
540 			    cur->wcb_name, cur->wcb_modname, cur->wcb_prio,
541 			    cur->wcb_func, cur->wcb_arg, curfl);
542 		}
543 		return (DCMD_OK);
544 	}
545 
546 	if (!(flags & DCMD_ADDRSPEC))
547 		return (DCMD_USAGE);
548 
549 	w.w_addrs = &addr;
550 	w.w_naddrs = 1;
551 
552 	/* If our input is a pipe, try to slurp it all up. */
553 	if (!keep && (flags & DCMD_PIPE)) {
554 		mdb_pipe_t p;
555 		mdb_get_pipe(&p);
556 
557 		if (p.pipe_len != 0) {
558 			w.w_addrs = p.pipe_data;
559 			w.w_naddrs = p.pipe_len;
560 
561 			/* sort the address list */
562 			qsort(w.w_addrs, w.w_naddrs, sizeof (*w.w_addrs),
563 			    uintptr_cmp);
564 		}
565 	}
566 	w.w_addrfound = mdb_zalloc(w.w_naddrs * sizeof (*w.w_addrfound),
567 	    UM_SLEEP | UM_GC);
568 
569 	if (whatis_debug) {
570 		mdb_printf("Searching for:\n");
571 		for (idx = 0; idx < w.w_naddrs; idx++)
572 			mdb_printf("    %p", w.w_addrs[idx]);
573 	}
574 
575 	ret = 0;
576 
577 	/* call in to the registered handlers */
578 	for (idx = 0; idx < whatis_cb_count; idx++) {
579 		whatis_callback_t *cur = whatis_cb[idx];
580 
581 		/* Honor the ident flags */
582 		if (w.w_flags & WHATIS_IDSPACE) {
583 			if (cur->wcb_flags & WHATIS_REG_NO_ID)
584 				continue;
585 		} else {
586 			if (cur->wcb_flags & WHATIS_REG_ID_ONLY)
587 				continue;
588 		}
589 
590 		if (w.w_flags & WHATIS_VERBOSE)
591 			mdb_printf("Searching %s`%s...\n",
592 			    cur->wcb_modname, cur->wcb_name);
593 
594 		if (cur->wcb_func(&w, cur->wcb_arg) != 0)
595 			ret = 1;
596 
597 		/* reset the match state for the next callback */
598 		w.w_match_next = 0;
599 		w.w_match_base = 0;
600 		w.w_match_size = 0;
601 
602 		if (w.w_done)
603 			break;
604 	}
605 
606 	/* Report any unexplained pointers */
607 	for (idx = 0; idx < w.w_naddrs; idx++) {
608 		uintptr_t addr = w.w_addrs[idx];
609 
610 		if (w.w_addrfound[idx])
611 			continue;
612 
613 		mdb_whatis_report_object(&w, addr, addr, "unknown\n");
614 	}
615 
616 	return ((ret != 0) ? DCMD_ERR : DCMD_OK);
617 }
618 
619 void
whatis_help(void)620 whatis_help(void)
621 {
622 	int idx;
623 
624 	mdb_printf("%s\n",
625 "Given a virtual address (with -i, an identifier), report where it came\n"
626 "from.\n"
627 "\n"
628 "When fed from a pipeline, ::whatis will not maintain the order the input\n"
629 "comes in; addresses will be reported as it finds them. (-k prevents this;\n"
630 "the output will be in the same order as the input)\n");
631 	(void) mdb_dec_indent(2);
632 	mdb_printf("%<b>OPTIONS%</b>\n");
633 	(void) mdb_inc_indent(2);
634 	mdb_printf("%s",
635 "  -a  Report all information about each address/identifier.  The default\n"
636 "      behavior is to report only the first (most specific) source for each\n"
637 "      address/identifier.\n"
638 "  -i  addr is an identifier, not a virtual address.\n"
639 "  -k  Do not re-order the input. (may be slower)\n"
640 "  -q  Quiet; don't print multi-line reports. (stack traces, etc.)\n"
641 "  -v  Verbose output; display information about the progress of the search\n");
642 
643 	if (mdb.m_lmod != NULL)
644 		return;
645 
646 	(void) mdb_dec_indent(2);
647 	mdb_printf("\n%<b>SOURCES%</b>\n\n");
648 	(void) mdb_inc_indent(2);
649 	mdb_printf("The following information sources will be used:\n\n");
650 
651 	(void) mdb_inc_indent(2);
652 	for (idx = 0; idx < whatis_cb_count; idx++) {
653 		whatis_callback_t *cur = whatis_cb[idx];
654 
655 		mdb_printf("%s`%s\n", cur->wcb_modname, cur->wcb_name);
656 	}
657 	(void) mdb_dec_indent(2);
658 }
659