xref: /illumos-gate/usr/src/cmd/amt/amt.c (revision ab42163696e52469cde1ec6102c5e82ce96474bd)
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 /*
28  * Abstract Machine Test; executes memory access tests to show
29  * compliance with Common Criteria object reuse and process address
30  * space separation requirements.
31  */
32 #include <errno.h>
33 #include <fcntl.h>
34 #include <iso/stdlib_iso.h>
35 #include <libelf.h>
36 #include <libintl.h>
37 #include <locale.h>
38 #include <signal.h>
39 #include <stdio.h>
40 #include <string.h>
41 #include <sys/param.h>
42 #include <sys/resource.h>
43 #include <sys/sysmacros.h>
44 #include <sys/types.h>
45 #include <unistd.h>
46 #include <wait.h>
47 
48 #define	NOT_SILENT 0
49 #define	SILENT_MODE 1
50 
51 #define	CHILD_SLEEP_PERIOD 2
52 #define	PARENT_SLEEP_PERIOD 1
53 #define	SIG_EVENT SIGUSR1
54 
55 #define	PASS	   0		/* test passed, no SEGV */
56 #define	FAIL_ZERO  1		/* expected to read zero, didn't */
57 #define	FAIL_SEGV  2		/* expected good read or write, didn't */
58 #define	PASS_SEGV  3		/* expected SEGV, got it */
59 #define	FAIL_ABORT 4		/* test logic error */
60 
61 #define	PH_VALID   0		/* arg for probe_hole -- do valid memory */
62 				/* access */
63 #define	PH_INVALID 1		/* do illegal memory access */
64 
65 #define	WASTE_PAGES   8		/* a guess at where virgin stack space */
66 				/* is likely to exist */
67 #define	STACK_SLOP  256		/* a guess at how far below current end */
68 				/* of stack I'll find unused space */
69 
70 #if !defined(TEXT_DOMAIN)
71 #define	TEXT_DOMAIN "SYS_TEST"
72 #endif
73 
74 extern int _end;	/* first address after the end of initialized data */
75 
76 static int  data_boundary_test();
77 static void handler(int);
78 static int  memory_not_shared_after_use();
79 static int  memory_allocation_not_shared();
80 static int  memory_type(const char *);
81 static void print_message(char *);
82 static void probe_data_area(void);
83 static void probe_hole(int);
84 static void probe_stack(void);
85 static void probe_text_area(void);
86 static void segv_action(int, siginfo_t *, void *);
87 static void set_handler(int);
88 static int  test_stack_end_of_hole();
89 static int  text_area_not_writeable();
90 
91 static int done_memory_grab = 0;
92 static int silent;
93 static int handler_exit_code;
94 
95 /*
96  * Main Routine
97  */
98 int
99 main(int argc, char *argv[])
100 {
101 	int fail_count = 0;
102 	int status = 0;
103 	int bitsize;
104 
105 	/* Internationalization */
106 	(void) setlocale(LC_ALL, "");
107 	(void) textdomain(TEXT_DOMAIN);
108 
109 	silent = NOT_SILENT;
110 
111 	if (argc == 2) {
112 		/* Pull out argument provided		 */
113 		/* -s	silent mode, no status or error messages. */
114 		if (strncmp(argv[1], "-s", 4) == 0)
115 			silent = SILENT_MODE;
116 		else {
117 			/* Wrong argument */
118 			(void) fprintf(stderr, gettext(
119 			    "Wrong argument, USAGE: amt [-s]\n"));
120 			exit(EXIT_FAILURE);
121 		}
122 	} else if (argc != 1) {
123 		/* Illegal number of arguments. */
124 		(void) fprintf(stderr, gettext(
125 		    "Wrong usage, USAGE: amt [-s]\n"));
126 		exit(EXIT_FAILURE);
127 	}
128 	bitsize = memory_type(argv[0]);
129 
130 	if (silent == NOT_SILENT)
131 		(void) printf(gettext(
132 		    "\n\nAMT Test Program -- %d bit application\n"
133 		    "================\n"), bitsize);
134 	/*
135 	 * test_stack_end_of_hole must be the first test, or the stack
136 	 * is of an unknown size.
137 	 */
138 	if ((status = test_stack_end_of_hole()) == EXIT_FAILURE) {
139 		/* Normal fail */
140 		fail_count++;
141 		print_message(gettext("TEST 1 FAILED\n"));
142 	} else if (status == FAIL_ABORT) {
143 		/* Test logic failure */
144 		fail_count++;
145 		print_message(gettext("FAIL: Logic error in test\n"));
146 	} else if (status == EXIT_SUCCESS)
147 		print_message(gettext("TEST 1 PASSED\n"));
148 
149 	/* Carry out test 2 */
150 	if (data_boundary_test() != EXIT_SUCCESS) {
151 		fail_count++;
152 		print_message(gettext("TEST 2 FAILED\n"));
153 	} else
154 		print_message(gettext("TEST 2 PASSED\n"));
155 
156 	/* Carry out test 3 */
157 	if (text_area_not_writeable() != EXIT_SUCCESS) {
158 		fail_count++;
159 		print_message(gettext("TEST 3 FAILED\n"));
160 	} else
161 		print_message(gettext("TEST 3 PASSED\n"));
162 
163 	/* Carry out test 4 */
164 	if (memory_not_shared_after_use() != EXIT_SUCCESS) {
165 		fail_count++;
166 		print_message(gettext("TEST 4 FAILED\n"));
167 	} else
168 		print_message(gettext("TEST 4 PASSED\n"));
169 
170 	/* Carry out test 5 */
171 	if (memory_allocation_not_shared() != EXIT_SUCCESS) {
172 		fail_count++;
173 		print_message(gettext("TEST 5 FAILED\n"));
174 	} else
175 		print_message(gettext("TEST 5 PASSED\n"));
176 
177 	if (silent == NOT_SILENT) {
178 		if (fail_count > 0)
179 			(void) printf(gettext("\n %d TESTS FAILED\n\n"),
180 			    fail_count);
181 		else
182 			(void) printf(gettext("\nTESTS SUCCEEDED\n\n"));
183 	}
184 	return (fail_count);
185 }
186 
187 /*
188  * Test the data boundaries. First test inside the data area at the boundary
189  * of the "hole" area. Second test inside the data area at the text area
190  * boundary. Both should pass.
191  */
192 static int
193 data_boundary_test()
194 {
195 	int exit_status = EXIT_SUCCESS;
196 	pid_t pid;
197 	int status;
198 
199 	print_message(gettext("\n\nTest 2- Data Side Boundary Test.\n"));
200 
201 	if ((pid = fork()) == -1) {
202 		print_message(gettext("Fork failed\n"));
203 		return (EXIT_FAILURE);
204 	} else if (pid == 0) { /* Am I my child? */
205 		set_handler(SIGSEGV);
206 
207 		/* probe_data_area() does exit() */
208 		probe_data_area();
209 	}
210 	/* still parent */
211 	(void) wait(&status);
212 	status = WEXITSTATUS(status);
213 
214 	if (status == PASS)
215 		print_message(gettext(
216 		    "PASS: Successful read/write in data area.\n"));
217 	else if (status == FAIL_SEGV) {
218 		print_message(gettext(
219 		    "FAIL: Caught a segmentation fault while "
220 		    "attempting to write to the data area.\n"));
221 		exit_status = EXIT_FAILURE;
222 	} else {
223 		(void) printf(gettext("Test program failure: %d\n"),
224 		    status);
225 		exit_status = EXIT_FAILURE;
226 	}
227 	return (exit_status);
228 }
229 
230 static void
231 probe_data_area()
232 {
233 	int *p;
234 	/* LINTED */
235 	volatile int p1 __unused;
236 	void *address;
237 
238 	/* set handler status */
239 	handler_exit_code = FAIL_SEGV;
240 
241 	/*
242 	 * Get an address in the data area, near to the "hole".
243 	 * sbrk returns prior address value; rather than calculating
244 	 * the sbrk result, sbrk is called twice, so address points
245 	 * to the new end of data
246 	 */
247 	(void) sbrk(PAGESIZE);
248 	address = sbrk(0);
249 	/*
250 	 * Get to the inside edge of a page boundary
251 	 * two integer words short of a new page
252 	 */
253 	p = ((int *)P2ROUNDUP((uintptr_t)address, PAGESIZE)) - 2;
254 
255 	/* Try writing to it, shouldn't cause a segmentation fault. */
256 	*p = 9999;
257 
258 	/* Should be able to read back with no problems. */
259 	p1 = *p;
260 
261 	/*
262 	 * Get an address near the text area boundary, but in the data
263 	 * area.  _etext rounded up a page isn't correct since the
264 	 * initialized data area isn't writeable.
265 	 *
266 	 * Future versions should consider handling initialized data
267 	 * separately -- writing to initialized data should generate
268 	 * a fault.
269 	 */
270 	p = &_end;
271 
272 	/* Try writing to it, should succeed. */
273 	*p = 9898;
274 
275 	/* Should be able to read back with no problems. */
276 	p1 = *p;
277 
278 	exit(EXIT_SUCCESS);
279 }
280 
281 /*
282  * Test that we cannot write to the text area. An attempt to write to
283  * the text area will result in a segmentation fault. So if we catch it,
284  * test has succeed, else it has failed.
285  */
286 static int
287 text_area_not_writeable()
288 {
289 	int exit_status = EXIT_SUCCESS;
290 	pid_t pid;
291 	int status;
292 
293 	print_message(gettext(
294 	    "\n\nTest 3- Text Area Not Writeable\n"
295 	    "Verify that a write to the text space does not cause "
296 	    "a write to the executable\n"
297 	    "file from which it came, or to another process which "
298 	    "shares that text.\n"));
299 
300 	if ((pid = fork()) == -1) {
301 		print_message(gettext("Fork failed\n"));
302 		return (EXIT_FAILURE);
303 	} else if (pid == 0) { /* Am I my child? */
304 		set_handler(SIGSEGV);
305 
306 		/* probe_text_area() does exit() */
307 		probe_text_area();
308 	}
309 	/* still parent */
310 	(void) wait(&status);
311 	status = WEXITSTATUS(status);
312 
313 	if (status == PASS) {
314 		print_message(gettext(
315 		    "FAIL: We did not cause a segmentation fault.\n"));
316 		exit_status = EXIT_FAILURE;
317 	} else if (status == FAIL_SEGV) {
318 		print_message(gettext(
319 		    "PASS: Caught the segmentation fault, "
320 		    "meaning we can't write to text area.\n"));
321 	} else {
322 		(void) printf(gettext(
323 		    "Test program failure: %d\n"), status);
324 		exit_status = EXIT_FAILURE;
325 	}
326 	return (exit_status);
327 }
328 
329 /*
330  * write to text area, trigger a SEGV
331  */
332 static void
333 probe_text_area()
334 {
335 	handler_exit_code = FAIL_SEGV;
336 	*(caddr_t)probe_text_area = 0xff;
337 	exit(EXIT_FAILURE);
338 }
339 
340 /*
341  * Test that when we set some values and fork a process, when the child
342  * writes to these inherited values, the parents copies are not changed.
343  */
344 static int
345 memory_not_shared_after_use()
346 {
347 	pid_t pid;
348 	int x = 1000;
349 	int exit_status = EXIT_SUCCESS;
350 
351 	print_message(gettext("\n\nTest 4- Memory Not Shared After Write\n"
352 	    "Verify that anonymous memory initially shared by two "
353 	    "processes (e.g. after a\n"
354 	    "fork) is not shared after either process writes "
355 	    "to it.\n"));
356 
357 	if ((pid = fork()) == -1) {
358 		print_message(gettext("Fork failed\n"));
359 		return (EXIT_FAILURE);
360 	} else if (pid == 0) { /* I am the child. */
361 		/*
362 		 * Change child value; this should not change
363 		 * parent value.
364 		 */
365 		x = 2000;
366 
367 		/* Wait for parent to test value */
368 		(void) sleep(CHILD_SLEEP_PERIOD);
369 
370 		exit(EXIT_SUCCESS);
371 	}
372 	/* Wait for child to do its stuff. */
373 	(void) sleep(PARENT_SLEEP_PERIOD);
374 
375 	if (x == 1000)
376 		exit_status = EXIT_SUCCESS;
377 	else
378 		exit_status = EXIT_FAILURE;
379 
380 	return (exit_status);
381 }
382 
383 /*
384  * If we fork a process and then allocate some memory in that process,
385  * we should not see any memory changes in the parent.
386  */
387 static int
388 memory_allocation_not_shared()
389 {
390 	pid_t pid;
391 	pid_t parent_pid;
392 	int exit_status = 0;
393 	caddr_t address;
394 	caddr_t hole_start;
395 	caddr_t hole_after;
396 	void (*old_handler) ();
397 
398 	print_message(gettext(
399 	    "\n\nTest 5- Memory Allocation is Not Shared\n"
400 	    "Verify that newly allocated memory in one of two "
401 	    "processes created by forking\n"
402 	    "does not result in newly allocated memory in the other.\n"));
403 
404 	/* Save Size of data area and 1st block address of "hole" */
405 	hole_start = (caddr_t)sbrk(0);
406 
407 	if (silent == NOT_SILENT)
408 		(void) printf(gettext(
409 		    "Parent address of hole before child change: %08X\n"),
410 		    hole_start);
411 
412 	/* Set handler for signal SIG_EVENT (define at start) */
413 	old_handler = signal(SIG_EVENT, &handler);
414 	if (old_handler == SIG_ERR) {
415 		print_message(gettext(
416 		    "Can't establish signal handler, test failed\n"));
417 		return (EXIT_FAILURE);
418 	}
419 
420 	if ((pid = fork()) == -1) {
421 		print_message(gettext("Fork failed\n"));
422 		return (EXIT_FAILURE);
423 	} else if (pid == 0) { /* We are the child. */
424 		address = sbrk(0);
425 		if (silent == NOT_SILENT)
426 			(void) printf(gettext(
427 			    "Child end of hole before change:  %08X\n"),
428 			    address);
429 
430 		if (brk((address+PAGESIZE)) != 0) {
431 			print_message(gettext(
432 			    "Can't change start of hole address.\n"));
433 			exit(EXIT_FAILURE);
434 		}
435 
436 		address = sbrk(0);
437 		if (silent == NOT_SILENT)
438 			(void) printf(gettext(
439 			    "Child end of hole after change: %08X\n"),
440 			    address);
441 
442 		/* Tell the parent we're done. */
443 		parent_pid = getppid();
444 		if (sigsend(P_PID, parent_pid, SIG_EVENT) != 0) {
445 			print_message(gettext("Can't send signal to parent, "
446 			    "test failed\n"));
447 			exit(EXIT_FAILURE);
448 		}
449 
450 		/* Sleep before exiting to allow parent to finish processing. */
451 		(void) sleep(CHILD_SLEEP_PERIOD);
452 		exit(EXIT_SUCCESS);
453 	}
454 	/* Wait for child to do its work. */
455 	(void) sleep(PARENT_SLEEP_PERIOD);
456 
457 	if (done_memory_grab != 1) {
458 		print_message(gettext(
459 		    "Child failed to do memory alterations, "
460 		    "exiting\n"));
461 		return (EXIT_FAILURE);
462 	}
463 
464 	hole_after = sbrk(0);
465 	if (silent == NOT_SILENT)
466 		(void) printf(gettext(
467 		    "Parent address of hole after child change: "
468 		    "%08X\n"), hole_after);
469 
470 	/* Test size of hole and data region. */
471 	if (hole_start == hole_after)
472 		print_message(gettext(
473 		    "PASS: Hole is same size in parent.\n"));
474 	else {
475 		print_message(gettext(
476 		    "FAIL: Hole is a different size.\n"));
477 		exit_status = EXIT_FAILURE;
478 	}
479 
480 	/* Wait for child to finish. */
481 	(void) wait(0);
482 
483 	if (signal(SIG_EVENT, old_handler) == SIG_ERR) {
484 		print_message(gettext("Couldn't put back old signal handler, "
485 		    "test failed.\n"));
486 		return (EXIT_FAILURE);
487 	}
488 	return (exit_status);
489 }
490 
491 static void
492 print_message(char *message)
493 {
494 	if (silent == NOT_SILENT)
495 		(void) printf("%s", message);
496 }
497 
498 static int
499 test_stack_end_of_hole()
500 {
501 	pid_t pid;
502 	int status;
503 	int exit_status = EXIT_SUCCESS;
504 
505 	print_message(gettext("\n\nTest 1- stack Side Boundary Test\n"));
506 
507 	/* sub test 1:  the space the stack grows into is zero */
508 
509 	if ((pid = fork()) == -1) {
510 		print_message(gettext("Fork failed\n"));
511 		return (EXIT_FAILURE);
512 	} else if (pid == 0) { /* Am I my child? */
513 		set_handler(SIGSEGV);
514 
515 		/* probe_stack() does exit */
516 		probe_stack();
517 	}
518 	/* still parent */
519 	(void) wait(&status);
520 	status = WEXITSTATUS(status);
521 
522 	if (status == FAIL_ZERO) {
523 		print_message(gettext("Fail with non-zero read.\n"));
524 		exit_status = EXIT_FAILURE;
525 	} else if (status != PASS) {
526 		print_message(gettext("Test program failure\n"));
527 		exit_status = EXIT_FAILURE;
528 	}
529 	/* sub test 2:  the space in hole is not readable */
530 
531 	if ((pid = fork()) == -1) {
532 		print_message(gettext("Fork failed\n"));
533 		return (EXIT_FAILURE);
534 	} else if (pid == 0) { /* Am I my child? */
535 		set_handler(SIGSEGV);
536 
537 		/* probe_hole does exit */
538 		probe_hole(PH_INVALID);
539 	}
540 	/* still parent */
541 	(void) wait(&status);
542 	status = WEXITSTATUS(status);
543 
544 	if (status == FAIL_SEGV) {
545 		print_message(
546 		    gettext("Fail (SEGV expected, not received).\n"));
547 		exit_status = EXIT_FAILURE;
548 	} else if (status != PASS_SEGV) {
549 		print_message(gettext("Test program failure.\n"));
550 		exit_status = EXIT_FAILURE;
551 	}
552 
553 	/* sub test 3:  the space in new page below hole is zero */
554 
555 	if ((pid = fork()) == -1) {
556 		print_message(gettext("Fork failed\n"));
557 		return (EXIT_FAILURE);
558 	} else if (pid == 0) { /* Am I my child? */
559 		set_handler(SIGSEGV);
560 
561 		/* probe_hole does exit */
562 		probe_hole(PH_VALID);
563 	}
564 	/* still parent */
565 	(void) wait(&status);
566 	status = WEXITSTATUS(status);
567 
568 	if (status == FAIL_SEGV) {
569 		print_message(gettext("Fail (got SEGV).\n"));
570 		exit_status = EXIT_FAILURE;
571 	} else if (status != PASS) {
572 		print_message(gettext("Test program failure.\n"));
573 		exit_status = EXIT_FAILURE;
574 	}
575 	return (exit_status);
576 }
577 
578 
579 /*
580  * set_handler
581  */
582 static void
583 set_handler(int sig)
584 {
585 	struct sigaction act;
586 
587 	act.sa_handler = NULL;
588 	act.sa_flags = SA_SIGINFO;
589 	act.sa_sigaction = segv_action;
590 	(void) sigemptyset(&(act.sa_mask));
591 
592 	if (sigaction(sig, &act, NULL) < 0) {
593 		if (silent == NOT_SILENT) {
594 			(void) fprintf(stderr, gettext(
595 			    "sigaction() returned error: %s\n"),
596 			    strerror(errno));
597 		}
598 		exit(EXIT_FAILURE);
599 	}
600 }
601 
602 
603 /*ARGSUSED*/
604 static void
605 segv_action(int which_sig, siginfo_t *t1, void *t2)
606 {
607 	exit(handler_exit_code);
608 }
609 
610 /*
611  * probe_stack
612  *
613  * Warning -- if you do a printf or fprintf prior to the actual
614  * reading from the stack, you've changed the stack to an unknown
615  * state.  (stack memory isn't free'd automatically and this function
616  * needs to touch virgin stack space.)
617  */
618 static void
619 probe_stack(void)
620 {
621 	unsigned char *end;	/* end of stack */
622 	unsigned char probe;
623 	long i;
624 	int j;
625 	unsigned char last_fail, *last_fail_address;
626 	unsigned char mark = 0xAA;	/* roughly the end of stack */
627 	handler_exit_code = FAIL_SEGV;
628 
629 	end = &mark;
630 	/* stack growth is negative */
631 	end -= (WASTE_PAGES * PAGESIZE) + STACK_SLOP;
632 
633 	for (i = 0, j = 0; i < PAGESIZE; i++) {
634 		if ((probe = *end) != 0) {
635 			j++;
636 			last_fail = probe;
637 			last_fail_address = end;
638 		}
639 		end--;
640 	}
641 
642 	if (j != 0) {
643 		if (silent == NOT_SILENT)
644 			(void) fprintf(stderr, gettext(
645 			    "probe_stack failed. address=0x%08X; "
646 			    "probe=0x%02X; content = %d\n"),
647 			    (caddr_t)last_fail_address, last_fail, j);
648 
649 		exit(FAIL_ZERO);    /* test failed at least once */
650 	}
651 	exit(EXIT_SUCCESS);
652 }
653 
654 static void
655 probe_hole(int test_type)
656 {
657 	long i;
658 	/* LINTED */
659 	volatile unsigned char probe __unused;
660 	unsigned char *probe_adr;
661 	void *address;
662 
663 	address = sbrk(0);  /* current end data + 1 */
664 
665 	if (address == (void *)-1) {
666 		print_message(gettext("Test program logic error\n"));
667 		exit(FAIL_ABORT);
668 	}
669 	if (test_type == PH_VALID) {
670 		/* show that access works inside the  hole */
671 		handler_exit_code = FAIL_SEGV;
672 
673 		probe_adr =  (unsigned char *)address - sizeof (char);
674 
675 		for (i = 0; i < PAGESIZE; i++)
676 			probe = *probe_adr--;
677 
678 		exit(EXIT_SUCCESS);
679 	} else {
680 		/* show that a trap occurs in the  hole */
681 		handler_exit_code = PASS_SEGV;
682 
683 		address = (void *)P2ROUNDUP((uintptr_t)address, PAGESIZE);
684 		probe_adr = (unsigned char *)address;
685 
686 		probe = *probe_adr;
687 		exit(FAIL_SEGV);	/* expected SEGV, didn't get it */
688 	}
689 }
690 
691 /*
692  * Catch signal, child to parent
693  */
694 /*ARGSUSED*/
695 void
696 handler(int signal)
697 {
698 	done_memory_grab = 1;
699 }
700 /*
701  * memory_type:  Determine whether a given executable file is compiled
702  * as 32 or 64 bit.
703  *
704  * The following code was stolen from isainfo (1)
705  */
706 
707 static int
708 memory_type(const char *path)
709 {
710 	char *idarray;
711 	Elf *elf;
712 	int d;
713 	int bits = 0;
714 
715 	if ((d = open(path, O_RDONLY)) < 0) {
716 		(void) fprintf(stderr,
717 		    "cannot open: %s -- %s\n",
718 		    path, strerror(errno));
719 		return (bits);
720 	}
721 
722 	if (elf_version(EV_CURRENT) == EV_NONE) {
723 		(void) fprintf(stderr,
724 		    "internal error: ELF library out of date?\n");
725 		(void) close(d);
726 		return (bits);
727 	}
728 
729 	elf = elf_begin(d, ELF_C_READ, (Elf *)0);
730 	if (elf_kind(elf) != ELF_K_ELF) {
731 		(void) elf_end(elf);
732 		(void) close(d);
733 		return (bits);
734 	}
735 
736 	idarray = elf_getident(elf, 0);
737 
738 	if (idarray[EI_CLASS] == ELFCLASS32) {
739 		bits = 32;
740 	} else if (idarray[EI_CLASS] == ELFCLASS64) {
741 		bits = 64;
742 	}
743 
744 	(void) elf_end(elf);
745 	(void) close(d);
746 	return (bits);
747 }
748