xref: /trueos/lib/libproc/proc_sym.c (revision f3fa4bdf8b98edb697d801e65b8b2bd542f15787)
1 /*-
2  * Copyright (c) 2010 The FreeBSD Foundation
3  * Copyright (c) 2008 John Birrell (jb@freebsd.org)
4  * All rights reserved.
5  *
6  * Portions of this software were developed by Rui Paulo under sponsorship
7  * from the FreeBSD Foundation.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28  * SUCH DAMAGE.
29  *
30  * $FreeBSD$
31  */
32 
33 #include <sys/types.h>
34 #include <sys/user.h>
35 
36 #include <assert.h>
37 #include <err.h>
38 #include <stdio.h>
39 #include <libgen.h>
40 #include <string.h>
41 #include <stdlib.h>
42 #include <fcntl.h>
43 #include <string.h>
44 #include <unistd.h>
45 #include <libutil.h>
46 
47 #include "_libproc.h"
48 
49 #ifndef NO_CXA_DEMANGLE
50 extern char *__cxa_demangle(const char *, char *, size_t *, int *);
51 #endif /* NO_CXA_DEMANGLE */
52 
53 static void	proc_rdl2prmap(rd_loadobj_t *, prmap_t *);
54 
55 static void
demangle(const char * symbol,char * buf,size_t len)56 demangle(const char *symbol, char *buf, size_t len)
57 {
58 #ifndef NO_CXA_DEMANGLE
59 	char *dembuf;
60 
61 	if (symbol[0] == '_' && symbol[1] == 'Z' && symbol[2]) {
62 		dembuf = __cxa_demangle(symbol, NULL, NULL, NULL);
63 		if (!dembuf)
64 			goto fail;
65 		strlcpy(buf, dembuf, len);
66 		free(dembuf);
67 		return;
68 	}
69 fail:
70 #endif /* NO_CXA_DEMANGLE */
71 	strlcpy(buf, symbol, len);
72 }
73 
74 static int
find_dbg_obj(const char * path)75 find_dbg_obj(const char *path)
76 {
77 	int fd;
78 	char dbg_path[PATH_MAX];
79 
80 	snprintf(dbg_path, sizeof(dbg_path),
81 	    "/usr/lib/debug/%s.debug", path);
82 	fd = open(dbg_path, O_RDONLY);
83 	if (fd > 0)
84 		return (fd);
85 	else
86 		return (open(path, O_RDONLY));
87 }
88 
89 static void
proc_rdl2prmap(rd_loadobj_t * rdl,prmap_t * map)90 proc_rdl2prmap(rd_loadobj_t *rdl, prmap_t *map)
91 {
92 	map->pr_vaddr = rdl->rdl_saddr;
93 	map->pr_size = rdl->rdl_eaddr - rdl->rdl_saddr;
94 	map->pr_offset = rdl->rdl_offset;
95 	map->pr_mflags = 0;
96 	if (rdl->rdl_prot & RD_RDL_R)
97 		map->pr_mflags |= MA_READ;
98 	if (rdl->rdl_prot & RD_RDL_W)
99 		map->pr_mflags |= MA_WRITE;
100 	if (rdl->rdl_prot & RD_RDL_X)
101 		map->pr_mflags |= MA_EXEC;
102 	strlcpy(map->pr_mapname, rdl->rdl_path,
103 	    sizeof(map->pr_mapname));
104 }
105 
106 char *
proc_objname(struct proc_handle * p,uintptr_t addr,char * objname,size_t objnamesz)107 proc_objname(struct proc_handle *p, uintptr_t addr, char *objname,
108     size_t objnamesz)
109 {
110 	size_t i;
111 	rd_loadobj_t *rdl;
112 
113 	for (i = 0; i < p->nobjs; i++) {
114 		rdl = &p->rdobjs[i];
115 		if (addr >= rdl->rdl_saddr && addr < rdl->rdl_eaddr) {
116 			strlcpy(objname, rdl->rdl_path, objnamesz);
117 			return (objname);
118 		}
119 	}
120 	return (NULL);
121 }
122 
123 prmap_t *
proc_obj2map(struct proc_handle * p,const char * objname)124 proc_obj2map(struct proc_handle *p, const char *objname)
125 {
126 	size_t i;
127 	prmap_t *map;
128 	rd_loadobj_t *rdl;
129 	char path[MAXPATHLEN];
130 
131 	rdl = NULL;
132 	for (i = 0; i < p->nobjs; i++) {
133 		basename_r(p->rdobjs[i].rdl_path, path);
134 		if (strcmp(path, objname) == 0) {
135 			rdl = &p->rdobjs[i];
136 			break;
137 		}
138 	}
139 	if (rdl == NULL) {
140 		if (strcmp(objname, "a.out") == 0 && p->rdexec != NULL)
141 			rdl = p->rdexec;
142 		else
143 			return (NULL);
144 	}
145 
146 	if ((map = malloc(sizeof(*map))) == NULL)
147 		return (NULL);
148 	proc_rdl2prmap(rdl, map);
149 	return (map);
150 }
151 
152 int
proc_iter_objs(struct proc_handle * p,proc_map_f * func,void * cd)153 proc_iter_objs(struct proc_handle *p, proc_map_f *func, void *cd)
154 {
155 	size_t i;
156 	rd_loadobj_t *rdl;
157 	prmap_t map;
158 	char path[MAXPATHLEN];
159 	char last[MAXPATHLEN];
160 
161 	if (p->nobjs == 0)
162 		return (-1);
163 	memset(last, 0, sizeof(last));
164 	for (i = 0; i < p->nobjs; i++) {
165 		rdl = &p->rdobjs[i];
166 		proc_rdl2prmap(rdl, &map);
167 		basename_r(rdl->rdl_path, path);
168 		/*
169 		 * We shouldn't call the callback twice with the same object.
170 		 * To do that we are assuming the fact that if there are
171 		 * repeated object names (i.e. different mappings for the
172 		 * same object) they occur next to each other.
173 		 */
174 		if (strcmp(path, last) == 0)
175 			continue;
176 		(*func)(cd, &map, path);
177 		strlcpy(last, path, sizeof(last));
178 	}
179 
180 	return (0);
181 }
182 
183 prmap_t *
proc_addr2map(struct proc_handle * p,uintptr_t addr)184 proc_addr2map(struct proc_handle *p, uintptr_t addr)
185 {
186 	size_t i;
187 	int cnt, lastvn = 0;
188 	prmap_t *map;
189 	rd_loadobj_t *rdl;
190 	struct kinfo_vmentry *kves, *kve;
191 
192 	/*
193 	 * If we don't have a cache of listed objects, we need to query
194 	 * it ourselves.
195 	 */
196 	if (p->nobjs == 0) {
197 		if ((kves = kinfo_getvmmap(p->pid, &cnt)) == NULL)
198 			return (NULL);
199 		for (i = 0; i < (size_t)cnt; i++) {
200 			kve = kves + i;
201 			if (kve->kve_type == KVME_TYPE_VNODE)
202 				lastvn = i;
203 			if (addr >= kve->kve_start && addr < kve->kve_end) {
204 				if ((map = malloc(sizeof(*map))) == NULL) {
205 					free(kves);
206 					return (NULL);
207 				}
208 				map->pr_vaddr = kve->kve_start;
209 				map->pr_size = kve->kve_end - kve->kve_start;
210 				map->pr_offset = kve->kve_offset;
211 				map->pr_mflags = 0;
212 				if (kve->kve_protection & KVME_PROT_READ)
213 					map->pr_mflags |= MA_READ;
214 				if (kve->kve_protection & KVME_PROT_WRITE)
215 					map->pr_mflags |= MA_WRITE;
216 				if (kve->kve_protection & KVME_PROT_EXEC)
217 					map->pr_mflags |= MA_EXEC;
218 				if (kve->kve_flags & KVME_FLAG_COW)
219 					map->pr_mflags |= MA_COW;
220 				if (kve->kve_flags & KVME_FLAG_NEEDS_COPY)
221 					map->pr_mflags |= MA_NEEDS_COPY;
222 				if (kve->kve_flags & KVME_FLAG_NOCOREDUMP)
223 					map->pr_mflags |= MA_NOCOREDUMP;
224 				strlcpy(map->pr_mapname, kves[lastvn].kve_path,
225 				    sizeof(map->pr_mapname));
226 				free(kves);
227 				return (map);
228 			}
229 		}
230 		free(kves);
231 		return (NULL);
232 	}
233 
234 	for (i = 0; i < p->nobjs; i++) {
235 		rdl = &p->rdobjs[i];
236 		if (addr >= rdl->rdl_saddr && addr < rdl->rdl_eaddr) {
237 			if ((map = malloc(sizeof(*map))) == NULL)
238 				return (NULL);
239 			proc_rdl2prmap(rdl, map);
240 			return (map);
241 		}
242 	}
243 	return (NULL);
244 }
245 
246 int
proc_addr2sym(struct proc_handle * p,uintptr_t addr,char * name,size_t namesz,GElf_Sym * symcopy)247 proc_addr2sym(struct proc_handle *p, uintptr_t addr, char *name,
248     size_t namesz, GElf_Sym *symcopy)
249 {
250 	Elf *e;
251 	Elf_Scn *scn, *dynsymscn = NULL, *symtabscn = NULL;
252 	Elf_Data *data;
253 	GElf_Shdr shdr;
254 	GElf_Sym sym;
255 	GElf_Ehdr ehdr;
256 	int fd, error = -1;
257 	size_t i;
258 	uint64_t rsym;
259 	prmap_t *map;
260 	char *s;
261 	unsigned long symtabstridx = 0, dynsymstridx = 0;
262 
263 	if ((map = proc_addr2map(p, addr)) == NULL)
264 		return (-1);
265 	if ((fd = find_dbg_obj(map->pr_mapname)) < 0) {
266 		DPRINTF("ERROR: open %s failed", map->pr_mapname);
267 		goto err0;
268 	}
269 	if ((e = elf_begin(fd, ELF_C_READ, NULL)) == NULL) {
270 		DPRINTFX("ERROR: elf_begin() failed: %s", elf_errmsg(-1));
271 		goto err1;
272 	}
273 	if (gelf_getehdr(e, &ehdr) == NULL) {
274 		DPRINTFX("ERROR: gelf_getehdr() failed: %s", elf_errmsg(-1));
275 		goto err2;
276 	}
277 	/*
278 	 * Find the index of the STRTAB and SYMTAB sections to locate
279 	 * symbol names.
280 	 */
281 	scn = NULL;
282 	while ((scn = elf_nextscn(e, scn)) != NULL) {
283 		gelf_getshdr(scn, &shdr);
284 		switch (shdr.sh_type) {
285 		case SHT_SYMTAB:
286 			symtabscn = scn;
287 			symtabstridx = shdr.sh_link;
288 			break;
289 		case SHT_DYNSYM:
290 			dynsymscn = scn;
291 			dynsymstridx = shdr.sh_link;
292 			break;
293 		default:
294 			break;
295 		}
296 	}
297 	/*
298 	 * Iterate over the Dynamic Symbols table to find the symbol.
299 	 * Then look up the string name in STRTAB (.dynstr)
300 	 */
301 	if ((data = elf_getdata(dynsymscn, NULL)) == NULL) {
302 		DPRINTFX("ERROR: elf_getdata() failed: %s", elf_errmsg(-1));
303 		goto symtab;
304 	}
305 	i = 0;
306 	while (gelf_getsym(data, i++, &sym) != NULL) {
307 		/*
308 		 * Calculate the address mapped to the virtual memory
309 		 * by rtld.
310 		 */
311 		if (ehdr.e_type != ET_EXEC)
312 			rsym = map->pr_vaddr + sym.st_value;
313 		else
314 			rsym = sym.st_value;
315 		if (addr >= rsym && addr < rsym + sym.st_size) {
316 			s = elf_strptr(e, dynsymstridx, sym.st_name);
317 			if (s) {
318 				demangle(s, name, namesz);
319 				memcpy(symcopy, &sym, sizeof(sym));
320 				/*
321 				 * DTrace expects the st_value to contain
322 				 * only the address relative to the start of
323 				 * the function.
324 				 */
325 				symcopy->st_value = rsym;
326 				error = 0;
327 				goto out;
328 			}
329 		}
330 	}
331 symtab:
332 	/*
333 	 * Iterate over the Symbols Table to find the symbol.
334 	 * Then look up the string name in STRTAB (.dynstr)
335 	 */
336 	if ((data = elf_getdata(symtabscn, NULL)) == NULL) {
337 		DPRINTFX("ERROR: elf_getdata() failed: %s", elf_errmsg(-1));
338 		goto err2;
339 	}
340 	i = 0;
341 	while (gelf_getsym(data, i++, &sym) != NULL) {
342 		/*
343 		 * Calculate the address mapped to the virtual memory
344 		 * by rtld.
345 		 */
346 		if (ehdr.e_type != ET_EXEC)
347 			rsym = map->pr_vaddr + sym.st_value;
348 		else
349 			rsym = sym.st_value;
350 		if (addr >= rsym && addr < rsym + sym.st_size) {
351 			s = elf_strptr(e, symtabstridx, sym.st_name);
352 			if (s) {
353 				demangle(s, name, namesz);
354 				memcpy(symcopy, &sym, sizeof(sym));
355 				/*
356 				 * DTrace expects the st_value to contain
357 				 * only the address relative to the start of
358 				 * the function.
359 				 */
360 				symcopy->st_value = rsym;
361 				error = 0;
362 				goto out;
363 			}
364 		}
365 	}
366 out:
367 err2:
368 	elf_end(e);
369 err1:
370 	close(fd);
371 err0:
372 	free(map);
373 	return (error);
374 }
375 
376 prmap_t *
proc_name2map(struct proc_handle * p,const char * name)377 proc_name2map(struct proc_handle *p, const char *name)
378 {
379 	size_t i;
380 	int cnt;
381 	prmap_t *map;
382 	char tmppath[MAXPATHLEN];
383 	struct kinfo_vmentry *kves, *kve;
384 	rd_loadobj_t *rdl;
385 
386 	/*
387 	 * If we haven't iterated over the list of loaded objects,
388 	 * librtld_db isn't yet initialized and it's very likely
389 	 * that librtld_db called us. We need to do the heavy
390 	 * lifting here to find the symbol librtld_db is looking for.
391 	 */
392 	if (p->nobjs == 0) {
393 		if ((kves = kinfo_getvmmap(proc_getpid(p), &cnt)) == NULL)
394 			return (NULL);
395 		for (i = 0; i < (size_t)cnt; i++) {
396 			kve = kves + i;
397 			basename_r(kve->kve_path, tmppath);
398 			if (strcmp(tmppath, name) == 0) {
399 				map = proc_addr2map(p, kve->kve_start);
400 				free(kves);
401 				return (map);
402 			}
403 		}
404 		free(kves);
405 		return (NULL);
406 	}
407 	if ((name == NULL || strcmp(name, "a.out") == 0) &&
408 	    p->rdexec != NULL) {
409 		map = proc_addr2map(p, p->rdexec->rdl_saddr);
410 		return (map);
411 	}
412 	for (i = 0; i < p->nobjs; i++) {
413 		rdl = &p->rdobjs[i];
414 		basename_r(rdl->rdl_path, tmppath);
415 		if (strcmp(tmppath, name) == 0) {
416 			if ((map = malloc(sizeof(*map))) == NULL)
417 				return (NULL);
418 			proc_rdl2prmap(rdl, map);
419 			return (map);
420 		}
421 	}
422 
423 	return (NULL);
424 }
425 
426 int
proc_name2sym(struct proc_handle * p,const char * object,const char * symbol,GElf_Sym * symcopy)427 proc_name2sym(struct proc_handle *p, const char *object, const char *symbol,
428     GElf_Sym *symcopy)
429 {
430 	Elf *e;
431 	Elf_Scn *scn, *dynsymscn = NULL, *symtabscn = NULL;
432 	Elf_Data *data;
433 	GElf_Shdr shdr;
434 	GElf_Sym sym;
435 	GElf_Ehdr ehdr;
436 	int fd, error = -1;
437 	size_t i;
438 	prmap_t *map;
439 	char *s;
440 	unsigned long symtabstridx = 0, dynsymstridx = 0;
441 
442 	if ((map = proc_name2map(p, object)) == NULL) {
443 		DPRINTFX("ERROR: couldn't find object %s", object);
444 		goto err0;
445 	}
446 	if ((fd = find_dbg_obj(map->pr_mapname)) < 0) {
447 		DPRINTF("ERROR: open %s failed", map->pr_mapname);
448 		goto err0;
449 	}
450 	if ((e = elf_begin(fd, ELF_C_READ, NULL)) == NULL) {
451 		DPRINTFX("ERROR: elf_begin() failed: %s", elf_errmsg(-1));
452 		goto err1;
453 	}
454 	if (gelf_getehdr(e, &ehdr) == NULL) {
455 		DPRINTFX("ERROR: gelf_getehdr() failed: %s", elf_errmsg(-1));
456 		goto err2;
457 	}
458 	/*
459 	 * Find the index of the STRTAB and SYMTAB sections to locate
460 	 * symbol names.
461 	 */
462 	scn = NULL;
463 	while ((scn = elf_nextscn(e, scn)) != NULL) {
464 		gelf_getshdr(scn, &shdr);
465 		switch (shdr.sh_type) {
466 		case SHT_SYMTAB:
467 			symtabscn = scn;
468 			symtabstridx = shdr.sh_link;
469 			break;
470 		case SHT_DYNSYM:
471 			dynsymscn = scn;
472 			dynsymstridx = shdr.sh_link;
473 			break;
474 		default:
475 			break;
476 		}
477 	}
478 	/*
479 	 * Iterate over the Dynamic Symbols table to find the symbol.
480 	 * Then look up the string name in STRTAB (.dynstr)
481 	 */
482 	if ((data = elf_getdata(dynsymscn, NULL))) {
483 		i = 0;
484 		while (gelf_getsym(data, i++, &sym) != NULL) {
485 			s = elf_strptr(e, dynsymstridx, sym.st_name);
486 			if (s && strcmp(s, symbol) == 0) {
487 				memcpy(symcopy, &sym, sizeof(sym));
488 				if (ehdr.e_type != ET_EXEC)
489 					symcopy->st_value += map->pr_vaddr;
490 				error = 0;
491 				goto out;
492 			}
493 		}
494 	}
495 	/*
496 	 * Iterate over the Symbols Table to find the symbol.
497 	 * Then look up the string name in STRTAB (.dynstr)
498 	 */
499 	if ((data = elf_getdata(symtabscn, NULL))) {
500 		i = 0;
501 		while (gelf_getsym(data, i++, &sym) != NULL) {
502 			s = elf_strptr(e, symtabstridx, sym.st_name);
503 			if (s && strcmp(s, symbol) == 0) {
504 				memcpy(symcopy, &sym, sizeof(sym));
505 				if (ehdr.e_type != ET_EXEC)
506 					symcopy->st_value += map->pr_vaddr;
507 				error = 0;
508 				goto out;
509 			}
510 		}
511 	}
512 out:
513 	DPRINTFX("found addr 0x%lx for %s", symcopy->st_value, symbol);
514 err2:
515 	elf_end(e);
516 err1:
517 	close(fd);
518 err0:
519 	free(map);
520 
521 	return (error);
522 }
523 
524 
525 int
proc_iter_symbyaddr(struct proc_handle * p,const char * object,int which,int mask,proc_sym_f * func,void * cd)526 proc_iter_symbyaddr(struct proc_handle *p, const char *object, int which,
527     int mask, proc_sym_f *func, void *cd)
528 {
529 	Elf *e;
530 	int i, fd;
531 	prmap_t *map;
532 	Elf_Scn *scn, *foundscn = NULL;
533 	Elf_Data *data;
534 	GElf_Ehdr ehdr;
535 	GElf_Shdr shdr;
536 	GElf_Sym sym;
537 	unsigned long stridx = -1;
538 	char *s;
539 	int error = -1;
540 
541 	if ((map = proc_name2map(p, object)) == NULL)
542 		return (-1);
543 	if ((fd = find_dbg_obj(map->pr_mapname)) < 0) {
544 		DPRINTF("ERROR: open %s failed", map->pr_mapname);
545 		goto err0;
546 	}
547 	if ((e = elf_begin(fd, ELF_C_READ, NULL)) == NULL) {
548 		DPRINTFX("ERROR: elf_begin() failed: %s", elf_errmsg(-1));
549 		goto err1;
550 	}
551 	if (gelf_getehdr(e, &ehdr) == NULL) {
552 		DPRINTFX("ERROR: gelf_getehdr() failed: %s", elf_errmsg(-1));
553 		goto err2;
554 	}
555 	/*
556 	 * Find the section we are looking for.
557 	 */
558 	scn = NULL;
559 	while ((scn = elf_nextscn(e, scn)) != NULL) {
560 		gelf_getshdr(scn, &shdr);
561 		if (which == PR_SYMTAB &&
562 		    shdr.sh_type == SHT_SYMTAB) {
563 			foundscn = scn;
564 			break;
565 		} else if (which == PR_DYNSYM &&
566 		    shdr.sh_type == SHT_DYNSYM) {
567 			foundscn = scn;
568 			break;
569 		}
570 	}
571 	if (!foundscn)
572 		return (-1);
573 	stridx = shdr.sh_link;
574 	if ((data = elf_getdata(foundscn, NULL)) == NULL) {
575 		DPRINTFX("ERROR: elf_getdata() failed: %s", elf_errmsg(-1));
576 		goto err2;
577 	}
578 	i = 0;
579 	while (gelf_getsym(data, i++, &sym) != NULL) {
580 		if (GELF_ST_BIND(sym.st_info) == STB_LOCAL &&
581 		    (mask & BIND_LOCAL) == 0)
582 			continue;
583 		if (GELF_ST_BIND(sym.st_info) == STB_GLOBAL &&
584 		    (mask & BIND_GLOBAL) == 0)
585 			continue;
586 		if (GELF_ST_BIND(sym.st_info) == STB_WEAK &&
587 		    (mask & BIND_WEAK) == 0)
588 			continue;
589 		if (GELF_ST_TYPE(sym.st_info) == STT_NOTYPE &&
590 		    (mask & TYPE_NOTYPE) == 0)
591 			continue;
592 		if (GELF_ST_TYPE(sym.st_info) == STT_OBJECT &&
593 		    (mask & TYPE_OBJECT) == 0)
594 			continue;
595 		if (GELF_ST_TYPE(sym.st_info) == STT_FUNC &&
596 		    (mask & TYPE_FUNC) == 0)
597 			continue;
598 		if (GELF_ST_TYPE(sym.st_info) == STT_SECTION &&
599 		    (mask & TYPE_SECTION) == 0)
600 			continue;
601 		if (GELF_ST_TYPE(sym.st_info) == STT_FILE &&
602 		    (mask & TYPE_FILE) == 0)
603 			continue;
604 		s = elf_strptr(e, stridx, sym.st_name);
605 		if (ehdr.e_type != ET_EXEC)
606 			sym.st_value += map->pr_vaddr;
607 		(*func)(cd, &sym, s);
608 	}
609 	error = 0;
610 err2:
611 	elf_end(e);
612 err1:
613 	close(fd);
614 err0:
615 	free(map);
616 	return (error);
617 }
618