xref: /illumos-gate/usr/src/lib/libtnfctl/comb.c (revision fba8753d)
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) 1994, by Sun Microsytems, Inc.
24  */
25 
26 /*
27  * Functions that know how to create and decode combinations that are
28  * used for connecting probe functions.
29  */
30 
31 #ifndef DEBUG
32 #define	NDEBUG	1
33 #endif
34 
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <search.h>
39 #include <assert.h>
40 #include <sys/types.h>
41 
42 #include "tnfctl_int.h"
43 #include "dbg.h"
44 
45 
46 /*
47  * Typedefs
48  */
49 
50 typedef struct comb_callinfo {
51 	unsigned	offset;
52 	unsigned	shift;	/* shift right <n> bits */
53 	unsigned	mask;
54 
55 } comb_callinfo_t;
56 
57 typedef struct comb_calltmpl {
58 	uintptr_t	entry;
59 	uintptr_t	down;
60 	uintptr_t	next;
61 	uintptr_t	end;
62 
63 } comb_calltmpl_t;
64 
65 typedef struct comb_key {
66 	comb_op_t	op;
67 	uintptr_t	down;
68 	uintptr_t	next;
69 	uintptr_t	comb;
70 } comb_key_t;
71 
72 typedef struct decode_key {
73 	uintptr_t	addr;
74 	char		**name_ptrs;
75 	uintptr_t	*func_addrs;
76 } decode_key_t;
77 
78 
79 /*
80  * Global - defined in assembler file
81  */
82 extern comb_callinfo_t prb_callinfo;
83 
84 extern void	 prb_chain_entry(void);
85 extern void	 prb_chain_down(void);
86 extern void	 prb_chain_next(void);
87 extern void	 prb_chain_end(void);
88 
89 static comb_calltmpl_t calltmpl[PRB_COMB_COUNT] = {
90 {
91 		(uintptr_t)prb_chain_entry,
92 		(uintptr_t)prb_chain_down,
93 		(uintptr_t)prb_chain_next,
94 		(uintptr_t)prb_chain_end}
95 };
96 
97 /*
98  * Declarations
99  */
100 
101 static tnfctl_errcode_t decode(tnfctl_handle_t *hndl, uintptr_t addr,
102 	char ***func_names, uintptr_t **func_addrs);
103 static boolean_t find(tnfctl_handle_t *hndl, comb_op_t op, uintptr_t down,
104 	uintptr_t next, uintptr_t *comb_p);
105 static tnfctl_errcode_t build(tnfctl_handle_t *hndl, comb_op_t op,
106 	uintptr_t down, uintptr_t next, uintptr_t *comb_p);
107 static tnfctl_errcode_t add(tnfctl_handle_t *hndl, comb_op_t op, uintptr_t down,
108 	uintptr_t next, uintptr_t comb);
109 static int comb_compare(const void *a, const void *b);
110 static int decode_compare(const void *v0p, const void *v1p);
111 static tnfctl_errcode_t iscomb(tnfctl_handle_t *hndl, uintptr_t addr,
112 	uintptr_t *down_p, uintptr_t *next_p, boolean_t *ret_val);
113 static tnfctl_errcode_t findname(tnfctl_handle_t *hndl, uintptr_t addr,
114 	char **ret_name);
115 
116 
117 /* ---------------------------------------------------------------- */
118 /* ----------------------- Public Functions ----------------------- */
119 /* ---------------------------------------------------------------- */
120 
121 /*
122  * _tnfctl_comb_build() - finds (or builds) a combination satisfing the op,
123  * down and next constraints of the caller.
124  */
125 tnfctl_errcode_t
_tnfctl_comb_build(tnfctl_handle_t * hndl,comb_op_t op,uintptr_t down,uintptr_t next,uintptr_t * comb_p)126 _tnfctl_comb_build(tnfctl_handle_t *hndl, comb_op_t op,
127     uintptr_t down, uintptr_t next, uintptr_t *comb_p)
128 {
129 	tnfctl_errcode_t	prexstat;
130 
131 	*comb_p = 0;
132 
133 	DBG_TNF_PROBE_0(_tnfctl_comb_build_start, "libtnfctl",
134 	    "start _tnfctl_comb_build; sunw%verbosity 1");
135 
136 	if (find(hndl, op, down, next, comb_p)) {
137 
138 		DBG_TNF_PROBE_1(_tnfctl_comb_build_end, "libtnfctl",
139 		    "end _tnfctl_comb_build; sunw%verbosity 1",
140 		    tnf_opaque, found_comb_at, *comb_p);
141 
142 		return (TNFCTL_ERR_NONE);
143 	}
144 	prexstat = build(hndl, op, down, next, comb_p);
145 
146 	DBG_TNF_PROBE_1(_tnfctl_comb_build_end, "libtnfctl",
147 	    "end _tnfctl_comb_build; sunw%verbosity 1",
148 	    tnf_opaque, built_comb_at, *comb_p);
149 
150 	return (prexstat);
151 }
152 
153 
154 /*
155  * _tnfctl_comb_decode() - returns a string describing the probe functions
156  * NOTE - the string is for reference purposes ONLY, it should not be freed
157  * by the client.
158  */
159 tnfctl_errcode_t
_tnfctl_comb_decode(tnfctl_handle_t * hndl,uintptr_t addr,char *** func_names,uintptr_t ** func_addrs)160 _tnfctl_comb_decode(tnfctl_handle_t *hndl, uintptr_t addr, char ***func_names,
161     uintptr_t **func_addrs)
162 {
163 	tnfctl_errcode_t prexstat;
164 
165 	DBG_TNF_PROBE_0(_tnfctl_comb_decode_start, "libtnfctl",
166 	    "start _tnfctl_comb_decode; sunw%verbosity 2");
167 
168 	prexstat = decode(hndl, addr, func_names, func_addrs);
169 
170 	DBG_TNF_PROBE_0(_tnfctl_comb_decode_end, "libtnfctl",
171 	    "end _tnfctl_comb_decode; sunw%verbosity 2");
172 
173 	return (prexstat);
174 }
175 
176 
177 /* ---------------------------------------------------------------- */
178 /* ----------------------- Private Functions ---------------------- */
179 /* ---------------------------------------------------------------- */
180 
181 /*
182  * if combination has been decoded, return decoded info., else
183  * decode combination and cache information
184  */
185 static tnfctl_errcode_t
decode(tnfctl_handle_t * hndl,uintptr_t addr,char *** func_names,uintptr_t ** func_addrs)186 decode(tnfctl_handle_t *hndl, uintptr_t addr, char ***func_names,
187     uintptr_t **func_addrs)
188 {
189 	tnfctl_errcode_t	prexstat = TNFCTL_ERR_NONE;
190 	decode_key_t	key;
191 	decode_key_t	*new_p = NULL;
192 	decode_key_t	**find_pp;
193 	uintptr_t	down;
194 	uintptr_t	next;
195 	char 		*thisname = NULL;
196 	boolean_t	is_combination;
197 
198 	/* see if we can find the previously decoded answer */
199 	key.addr = addr;
200 	find_pp = (decode_key_t **)tfind(&key, &hndl->decoderoot,
201 	    decode_compare);
202 	if (find_pp) {
203 		DBG_TNF_PROBE_0(decode_1, "libtnfctl",
204 		    "sunw%verbosity 2; sunw%debug 'found existing'");
205 		*func_names = (*find_pp)->name_ptrs;
206 		*func_addrs = (*find_pp)->func_addrs;
207 		return (TNFCTL_ERR_NONE);
208 	}
209 
210 	new_p = calloc(1, sizeof (decode_key_t));
211 	if (!new_p)
212 		return (TNFCTL_ERR_ALLOCFAIL);
213 	new_p->addr = addr;
214 
215 	prexstat = iscomb(hndl, addr, &down, &next, &is_combination);
216 	if (prexstat)
217 		goto Error;
218 
219 	if (is_combination) {
220 		char **nextnames;
221 		uintptr_t *nextaddrs;
222 		char **name_pp;
223 		uintptr_t *addr_p;
224 		int count, j;
225 
226 		DBG_TNF_PROBE_2(decode_2, "libtnfctl", "sunw%verbosity 2;",
227 		    tnf_opaque, down, down, tnf_opaque, next, next);
228 
229 		prexstat = findname(hndl, down, &thisname);
230 		if (prexstat == TNFCTL_ERR_USR1) {
231 			/*
232 			 * should never happen - combination should not
233 			 * point at the end function
234 			 */
235 			prexstat = TNFCTL_ERR_INTERNAL;
236 			goto Error;
237 		} else if (prexstat)
238 			goto Error;
239 
240 		prexstat = decode(hndl, next, &nextnames, &nextaddrs);
241 		if (prexstat)
242 			goto Error;
243 
244 		/* count number of elements - caution: empty 'for' loop */
245 		for (count = 0; nextnames[count]; count++)
246 			;
247 		count++;	/* since it was 0 based */
248 
249 		/* allocate one more for new function name */
250 		new_p->name_ptrs = malloc((count + 1) *
251 		    sizeof (new_p->name_ptrs[0]));
252 		if (new_p->name_ptrs == NULL) {
253 			prexstat = TNFCTL_ERR_ALLOCFAIL;
254 			goto Error;
255 		}
256 		new_p->func_addrs = malloc((count + 1) *
257 		    sizeof (new_p->func_addrs[0]));
258 		if (new_p->func_addrs == NULL) {
259 			prexstat = TNFCTL_ERR_ALLOCFAIL;
260 			goto Error;
261 		}
262 		name_pp = new_p->name_ptrs;
263 		addr_p = new_p->func_addrs;
264 		addr_p[0] = down;
265 		name_pp[0] = thisname;
266 		for (j = 0; j < count; j++) {
267 			name_pp[j + 1] = nextnames[j];
268 			addr_p[j + 1] = nextaddrs[j];
269 		}
270 	} else {
271 		prexstat = findname(hndl, addr, &thisname);
272 		if (prexstat != TNFCTL_ERR_USR1) {
273 			/*
274 			 * base case - end function is the only function
275 			 * that can be pointed at directly
276 			 */
277 			if (prexstat == TNFCTL_ERR_NONE)
278 				prexstat = TNFCTL_ERR_NONE;
279 			goto Error;
280 		}
281 		new_p->name_ptrs = malloc(sizeof (new_p->name_ptrs[0]));
282 		if (new_p->name_ptrs == NULL) {
283 			prexstat = TNFCTL_ERR_ALLOCFAIL;
284 			goto Error;
285 		}
286 		new_p->func_addrs = malloc(sizeof (new_p->func_addrs[0]));
287 		if (new_p->func_addrs == NULL) {
288 			prexstat = TNFCTL_ERR_ALLOCFAIL;
289 			goto Error;
290 		}
291 		new_p->name_ptrs[0] = NULL;
292 		new_p->func_addrs[0] = 0;
293 	}
294 
295 	DBG_TNF_PROBE_1(decode_3, "libtnfctl",
296 	    "sunw%verbosity 2; sunw%debug 'decode built'",
297 	    tnf_string, func_name, (thisname) ? (thisname) : "end_func");
298 
299 	find_pp = (decode_key_t **)tsearch(new_p, &hndl->decoderoot,
300 	    decode_compare);
301 	assert(*find_pp == new_p);
302 	*func_names = new_p->name_ptrs;
303 	*func_addrs = new_p->func_addrs;
304 	return (TNFCTL_ERR_NONE);
305 
306 Error:
307 	if (new_p) {
308 		if (new_p->name_ptrs)
309 			free(new_p->name_ptrs);
310 		if (new_p->func_addrs)
311 			free(new_p->func_addrs);
312 		free(new_p);
313 	}
314 	return (prexstat);
315 }
316 
317 
318 /*
319  * iscomb() - determine whether the pointed to function is a combination.  If
320  * it is, return the down and next pointers
321  */
322 static tnfctl_errcode_t
iscomb(tnfctl_handle_t * hndl,uintptr_t addr,uintptr_t * down_p,uintptr_t * next_p,boolean_t * ret_val)323 iscomb(tnfctl_handle_t *hndl, uintptr_t addr, uintptr_t *down_p,
324     uintptr_t *next_p, boolean_t *ret_val)
325 {
326 	int		type;
327 	boolean_t	matched = B_FALSE;
328 
329 	for (type = 0; type < PRB_COMB_COUNT; type++) {
330 		size_t		size;
331 		int		miscstat;
332 		char		*targ_p;
333 		char		*ptr;
334 		char		*tptr;
335 		uintptr_t	downaddr;
336 		uintptr_t	nextaddr;
337 		int		num_bits = 0;
338 		int		tmp_bits = prb_callinfo.mask;
339 
340 		/* allocate room to copy the target code */
341 		size = (size_t)(calltmpl[type].end - calltmpl[type].entry);
342 		targ_p = malloc(size);
343 		if (!targ_p)
344 			return (TNFCTL_ERR_ALLOCFAIL);
345 
346 		/* copy code from target */
347 		miscstat = hndl->p_read(hndl->proc_p, addr, targ_p, size);
348 		if (miscstat) {
349 			free(targ_p);
350 			return (TNFCTL_ERR_INTERNAL);
351 		}
352 
353 		/* find the number of bits before the highest bit in mask */
354 		while (tmp_bits > 0) {
355 			num_bits++;
356 			tmp_bits <<= 1;
357 		}
358 
359 		/* loop over all the words */
360 		tptr = (char *)calltmpl[type].entry;
361 		for (ptr = targ_p; ptr < (targ_p + size); ptr++, tptr++) {
362 			int			 downbits;
363 			int			 nextbits;
364 		/* LINTED pointer cast may result in improper alignment */
365 			int			*uptr = (int *)ptr;
366 
367 			/*
368 			 * If we are pointing at one of the words that we
369 			 * patch, * (down or next displ) then read that value
370 			 * in. * Otherwise make sure the words match.
371 			 */
372 			if ((uintptr_t)tptr == calltmpl[type].down +
373 			    prb_callinfo.offset) {
374 				downbits = *uptr;
375 				downbits &= prb_callinfo.mask;
376 				/* sign extend */
377 				downbits  = (downbits << num_bits) >> num_bits;
378 				downbits <<= prb_callinfo.shift;
379 				downaddr = addr + (ptr - targ_p) + downbits;
380 #if defined(i386)
381 				downaddr += 4;
382 				/* intel is relative to *next* instruction */
383 #endif
384 
385 				ptr += 3;
386 				tptr += 3;
387 			} else if ((uintptr_t)tptr == calltmpl[type].next +
388 			    prb_callinfo.offset) {
389 				nextbits = *uptr;
390 				nextbits &= prb_callinfo.mask;
391 				/* sign extend */
392 				nextbits  = (nextbits << num_bits) >> num_bits;
393 				nextbits <<= prb_callinfo.shift;
394 				nextaddr = addr + (ptr - targ_p) + nextbits;
395 #if defined(i386)
396 				nextaddr += 4;
397 				/* intel is relative to *next* instruction */
398 #endif
399 
400 				ptr += 3;
401 				tptr += 3;
402 			} else {
403 				/* the byte better match or we bail */
404 				if (*ptr != *tptr)
405 					goto NextComb;
406 			}
407 		}
408 
409 		/* YOWSA! - its a match */
410 		matched = B_TRUE;
411 
412 NextComb:
413 		/* free allocated memory */
414 		if (targ_p)
415 			free(targ_p);
416 
417 		if (matched) {
418 			*down_p = downaddr;
419 			*next_p = nextaddr;
420 			*ret_val = B_TRUE;
421 			return (TNFCTL_ERR_NONE);
422 		}
423 	}
424 
425 	*ret_val = B_FALSE;
426 	return (TNFCTL_ERR_NONE);
427 }
428 
429 
430 #define	FUNC_BUF_SIZE	32
431 /*
432  * findname() - find a name for a function given its address.
433  */
434 static tnfctl_errcode_t
findname(tnfctl_handle_t * hndl,uintptr_t addr,char ** ret_name)435 findname(tnfctl_handle_t *hndl, uintptr_t addr, char **ret_name)
436 {
437 	char		*symname;
438 	tnfctl_errcode_t prexstat;
439 
440 	symname = NULL;
441 	prexstat = _tnfctl_sym_findname(hndl, addr, &symname);
442 	if ((prexstat == TNFCTL_ERR_NONE) && (symname != NULL)) {
443 		/* found a name */
444 
445 		/*
446 		 * SPECIAL CASE
447 		 * If we find "tnf_trace_end" then we should not report it
448 		 * as this is the "end-cap" function and should be hidden
449 		 * from the user.  Return a null string instead ...
450 		 */
451 		if (strcmp(symname, TRACE_END_FUNC) == 0) {
452 			return (TNFCTL_ERR_USR1);
453 		} else {
454 			*ret_name = symname;
455 			return (TNFCTL_ERR_NONE);
456 		}
457 	} else {
458 		char *buffer;
459 
460 		buffer = malloc(FUNC_BUF_SIZE);
461 		if (buffer == NULL)
462 			return (TNFCTL_ERR_ALLOCFAIL);
463 
464 		/* no name found, use the address */
465 		(void) sprintf(buffer, "func@0x%p", addr);
466 		*ret_name = buffer;
467 		return (TNFCTL_ERR_NONE);
468 	}
469 }
470 
471 
472 /*
473  * find() - try to find an existing combination that satisfies ...
474  */
475 static boolean_t
find(tnfctl_handle_t * hndl,comb_op_t op,uintptr_t down,uintptr_t next,uintptr_t * comb_p)476 find(tnfctl_handle_t *hndl, comb_op_t op, uintptr_t down, uintptr_t next,
477     uintptr_t *comb_p)
478 {
479 	comb_key_t	key;
480 	comb_key_t	**find_pp;
481 
482 	key.op = op;
483 	key.down = down;
484 	key.next = next;
485 	key.comb = 0;
486 
487 	find_pp = (comb_key_t **)tfind(&key, &hndl->buildroot, comb_compare);
488 	if (find_pp) {
489 		*comb_p = (*find_pp)->comb;
490 		return (B_TRUE);
491 	} else
492 		return (B_FALSE);
493 }
494 
495 
496 /*
497  * add() - adds a combination to combination cache
498  */
499 static tnfctl_errcode_t
add(tnfctl_handle_t * hndl,comb_op_t op,uintptr_t down,uintptr_t next,uintptr_t comb)500 add(tnfctl_handle_t *hndl, comb_op_t op, uintptr_t down, uintptr_t next,
501     uintptr_t comb)
502 {
503 	comb_key_t	 *new_p;
504 	/* LINTED set but not used in function */
505 	comb_key_t	**ret_pp __unused;
506 
507 	new_p = malloc(sizeof (comb_key_t));
508 	if (!new_p)
509 		return (TNFCTL_ERR_ALLOCFAIL);
510 
511 	new_p->op = op;
512 	new_p->down = down;
513 	new_p->next = next;
514 	new_p->comb = comb;
515 
516 	ret_pp = (comb_key_t **)tsearch(new_p, &hndl->buildroot,
517 	    comb_compare);
518 	assert(*ret_pp == new_p);
519 	return (TNFCTL_ERR_NONE);
520 }
521 
522 
523 /*
524  * decode_compare() - comparison function used for tree search for
525  * combinations
526  */
527 static int
decode_compare(const void * v0p,const void * v1p)528 decode_compare(const void *v0p, const void *v1p)
529 {
530 	const decode_key_t   *k0p = v0p;
531 	const decode_key_t   *k1p = v1p;
532 
533 	return (int)((uintptr_t)k1p->addr - (uintptr_t)k0p->addr);
534 }				/* end decode_compare */
535 
536 
537 /*
538  * comb_compare() - comparison function used for tree search for combinations
539  */
540 static int
comb_compare(const void * v0p,const void * v1p)541 comb_compare(const void *v0p, const void *v1p)
542 {
543 	const comb_key_t *k0p = v0p;
544 	const comb_key_t *k1p = v1p;
545 
546 	if (k0p->op != k1p->op)
547 		return ((k0p->op < k1p->op) ? -1 : 1);
548 
549 	if (k0p->down != k1p->down)
550 		return ((k0p->down < k1p->down) ? -1 : 1);
551 
552 	if (k0p->next != k1p->next)
553 		return ((k0p->next < k1p->next) ? -1 : 1);
554 
555 	return (0);
556 
557 }				/* end comb_compare */
558 
559 /*
560  * build() - build a composition
561  */
562 static tnfctl_errcode_t
build(tnfctl_handle_t * hndl,comb_op_t op,uintptr_t down,uintptr_t next,uintptr_t * comb_p)563 build(tnfctl_handle_t *hndl, comb_op_t op, uintptr_t down, uintptr_t next,
564     uintptr_t *comb_p)
565 {
566 	size_t		size;
567 	uintptr_t	addr;
568 	char		*buffer_p = NULL;
569 	uintptr_t	offset;
570 	uintptr_t	contents;
571 	unsigned	*word_p;
572 	int		miscstat;
573 	tnfctl_errcode_t	prexstat;
574 
575 	*comb_p = 0;
576 	size = calltmpl[op].end - calltmpl[op].entry;
577 
578 	/* allocate memory in the target process */
579 	prexstat = _tnfctl_targmem_alloc(hndl, size, &addr);
580 	if (prexstat) {
581 		DBG((void) fprintf(stderr,
582 		    "build: trouble allocating target memory:\n"));
583 		goto Error;
584 	}
585 
586 	/* allocate a scratch buffer, copy the template into it */
587 	buffer_p = malloc(size);
588 	if (!buffer_p) {
589 		DBG((void) fprintf(stderr, "build: alloc failed\n"));
590 		prexstat = TNFCTL_ERR_ALLOCFAIL;
591 		goto Error;
592 	}
593 	(void) memcpy(buffer_p, (void *) calltmpl[op].entry, size);
594 
595 	/* poke the down address */
596 	offset = calltmpl[op].down - calltmpl[op].entry;
597 	/*LINTED pointer cast may result in improper alignment*/
598 	word_p = (unsigned *)(buffer_p + offset + prb_callinfo.offset);
599 	contents = down - (addr + offset);
600 #if defined(i386)
601 	contents -= 5;		/* intel offset is relative to *next* instr */
602 #endif
603 
604 	DBG_TNF_PROBE_4(build_1, "libtnfctl", "sunw%verbosity 3",
605 	    tnf_opaque, down, down, tnf_opaque, contents, contents,
606 	    tnf_opaque, word_p, word_p, tnf_long, offset, offset);
607 
608 	*word_p &= ~prb_callinfo.mask;	/* clear the relevant field */
609 	*word_p |= ((contents >> prb_callinfo.shift) & prb_callinfo.mask);
610 
611 	/* poke the next address */
612 	offset = calltmpl[op].next - calltmpl[op].entry;
613 	/*LINTED pointer cast may result in improper alignment*/
614 	word_p = (unsigned *)(buffer_p + offset + prb_callinfo.offset);
615 	contents = next - (addr + offset);
616 #if defined(i386)
617 	contents -= 5;		/* intel offset is relative to *next* instr */
618 #endif
619 
620 	DBG_TNF_PROBE_4(build_2, "libtnfctl", "sunw%verbosity 3",
621 	    tnf_opaque, next, next, tnf_opaque, contents, contents,
622 	    tnf_opaque, word_p, word_p, tnf_long, offset, offset);
623 
624 	*word_p &= ~prb_callinfo.mask;	/* clear the relevant field */
625 	*word_p |= ((contents >> prb_callinfo.shift) & prb_callinfo.mask);
626 
627 	/* copy the combination template into target memory */
628 	miscstat = hndl->p_write(hndl->proc_p, addr, buffer_p, size);
629 	if (miscstat) {
630 		DBG((void) fprintf(stderr,
631 		    "build: trouble writing combination: \n"));
632 		prexstat = TNFCTL_ERR_INTERNAL;
633 		goto Error;
634 	}
635 	*comb_p = addr;
636 	prexstat = add(hndl, op, down, next, addr);
637 
638 Error:
639 	if (buffer_p)
640 		free(buffer_p);
641 	return (prexstat);
642 }
643