2dc0093feschrock * CDDL HEADER START
3dc0093feschrock *
4dc0093feschrock * The contents of this file are subject to the terms of the
5dc0093feschrock * Common Development and Distribution License (the "License").
6dc0093feschrock * You may not use this file except in compliance with the License.
7dc0093feschrock *
8dc0093feschrock * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9dc0093feschrock * or http://www.opensolaris.org/os/licensing.
10dc0093feschrock * See the License for the specific language governing permissions
11dc0093feschrock * and limitations under the License.
12dc0093feschrock *
13dc0093feschrock * When distributing Covered Code, include this CDDL HEADER in each
14dc0093feschrock * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15dc0093feschrock * If applicable, add the following below this CDDL HEADER, with the
16dc0093feschrock * fields enclosed by brackets "[]" replaced with your own identifying
17dc0093feschrock * information: Portions Copyright [yyyy] [name of copyright owner]
18dc0093feschrock *
19dc0093feschrock * CDDL HEADER END
20dc0093feschrock */
2323a1cceRoger A. Faulkner * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
24b5f3c6fJason King *
25b5f3c6fJason King * Copyright 2011 Jason King.  All rights reserved.
26dc0093feschrock */
28dc0093feschrock#include <assert.h>
29dc0093feschrock#include <errno.h>
30dc0093feschrock#include <fcntl.h>
31dc0093feschrock#include <gelf.h>
32dc0093feschrock#include <libelf.h>
33dc0093feschrock#include <stdlib.h>
34dc0093feschrock#include <string.h>
35dc0093feschrock#include <unistd.h>
37dc0093feschrock#include <sys/fcntl.h>
38dc0093feschrock#include <sys/stat.h>
39b5f3c6fJason King#include <sys/sysmacros.h>
40b5f3c6fJason King#include <sys/types.h>
42dc0093feschrock#include "dis_target.h"
43dc0093feschrock#include "dis_util.h"
46dc0093feschrock * Standard ELF disassembler target.
47dc0093feschrock *
48dc0093feschrock * We only support disassembly of ELF files, though this target interface could
49dc0093feschrock * be extended in the future.  Each basic type (target, func, section) contains
50dc0093feschrock * enough information to uniquely identify the location within the file.  The
51dc0093feschrock * interfaces use libelf(3LIB) to do the actual processing of the file.
52dc0093feschrock */
55dc0093feschrock * Symbol table entry type.  We maintain our own symbol table sorted by address,
56dc0093feschrock * with the symbol name already resolved against the ELF symbol table.
57dc0093feschrock */
58dc0093feschrocktypedef struct sym_entry {
59dc0093feschrock	GElf_Sym	se_sym;		/* value of symbol */
60dc0093feschrock	char		*se_name;	/* name of symbol */
61dc0093feschrock	int		se_shndx;	/* section where symbol is located */
62dc0093feschrock} sym_entry_t;
65b5f3c6fJason King * Create a map of the virtual address ranges of every section.  This will
66b5f3c6fJason King * allow us to create dummpy mappings for unassigned addresses.  Otherwise
67b5f3c6fJason King * multiple sections with unassigned addresses will appear to overlap and
68b5f3c6fJason King * mess up symbol resolution (which uses the virtual address).
69b5f3c6fJason King */
70b5f3c6fJason Kingtypedef struct dis_shnmap {
71b5f3c6fJason King	const char 	*dm_name;	/* name of section */
72b5f3c6fJason King	uint64_t	dm_start;	/* virtual address of section */
73b5f3c6fJason King	size_t		dm_length;	/* address length */
74b5f3c6fJason King	boolean_t	dm_mapped;	/* did we assign the mapping */
75b5f3c6fJason King} dis_shnmap_t;
76b5f3c6fJason King
77b5f3c6fJason King/*
78dc0093feschrock * Target data structure.  This structure keeps track of the ELF file
79dc0093feschrock * information, a few bits of pre-processed section index information, and
80dc0093feschrock * sorted versions of the symbol table.  We also keep track of the last symbol
81dc0093feschrock * looked up, as the majority of lookups remain within the same symbol.
82dc0093feschrock */
83dc0093feschrockstruct dis_tgt {
84dc0093feschrock	Elf		*dt_elf;	/* libelf handle */
85dc0093feschrock	Elf		*dt_elf_root;	/* main libelf handle (for archives) */
86dc0093feschrock	const char	*dt_filename;	/* name of file */
87dc0093feschrock	int		dt_fd;		/* underlying file descriptor */
88dc0093feschrock	size_t		dt_shstrndx;	/* section index of .shstrtab */
89dc0093feschrock	size_t		dt_symidx;	/* section index of symbol table */
90dc0093feschrock	sym_entry_t	*dt_symcache;	/* last symbol looked up */
91dc0093feschrock	sym_entry_t	*dt_symtab;	/* sorted symbol table */
92dc0093feschrock	int		dt_symcount;	/* # of symbol table entries */
93dc0093feschrock	struct dis_tgt	*dt_next;	/* next target (for archives) */
94dc0093feschrock	Elf_Arhdr	*dt_arhdr;	/* archive header (for archives) */
95b5f3c6fJason King	dis_shnmap_t	*dt_shnmap;	/* section address map */
96b5f3c6fJason King	size_t		dt_shncount;	/* # of sections in target */
100dc0093feschrock * Function data structure.  We resolve the symbol and lookup the associated ELF
101dc0093feschrock * data when building this structure.  The offset is calculated based on the
102dc0093feschrock * section's starting address.
103dc0093feschrock */
104dc0093feschrockstruct dis_func {
105dc0093feschrock	sym_entry_t	*df_sym;	/* symbol table reference */
106dc0093feschrock	Elf_Data	*df_data;	/* associated ELF data */
107dc0093feschrock	size_t		df_offset;	/* offset within data */
111dc0093feschrock * Section data structure.  We store the entire section header so that we can
112dc0093feschrock * determine some properties (such as whether or not it contains text) after
113dc0093feschrock * building the structure.
114dc0093feschrock */
115dc0093feschrockstruct dis_scn {
116dc0093feschrock	GElf_Shdr	ds_shdr;
117dc0093feschrock	const char	*ds_name;
118dc0093feschrock	Elf_Data	*ds_data;
12123a1cceRoger A. Faulkner/* Lifted from Psymtab.c, omitting STT_TLS */
122dc0093feschrock#define	DATA_TYPES      \
12323a1cceRoger A. Faulkner	((1 << STT_OBJECT) | (1 << STT_FUNC) | (1 << STT_COMMON))
124dc0093feschrock#define	IS_DATA_TYPE(tp)	(((1 << (tp)) & DATA_TYPES) != 0)
127b5f3c6fJason King * Save the virtual address range for this section and select the
128b5f3c6fJason King * best section to use as the symbol table.  We prefer SHT_SYMTAB
129b5f3c6fJason King * over SHT_DYNSYM.
130dc0093feschrock */
131dc0093feschrock/* ARGSUSED */
132dc0093feschrockstatic void
133b5f3c6fJason Kingtgt_scn_init(dis_tgt_t *tgt, dis_scn_t *scn, void *data)
135dc0093feschrock	int *index = data;
137dc0093feschrock	*index += 1;
139b5f3c6fJason King	tgt->dt_shnmap[*index].dm_name = scn->ds_name;
140b5f3c6fJason King	tgt->dt_shnmap[*index].dm_start = scn->ds_shdr.sh_addr;
141b5f3c6fJason King	tgt->dt_shnmap[*index].dm_length = scn->ds_shdr.sh_size;
142b5f3c6fJason King	tgt->dt_shnmap[*index].dm_mapped = B_FALSE;
143b5f3c6fJason King
144dc0093feschrock	/*
145dc0093feschrock	 * Prefer SHT_SYMTAB over SHT_DYNSYM
146dc0093feschrock	 */
147dc0093feschrock	if (scn->ds_shdr.sh_type == SHT_DYNSYM && tgt->dt_symidx == 0)
148dc0093feschrock		tgt->dt_symidx = *index;
149dc0093feschrock	else if (scn->ds_shdr.sh_type == SHT_SYMTAB)
150dc0093feschrock		tgt->dt_symidx = *index;
153dc0093feschrockstatic int
154dc0093feschrocksym_compare(const void *a, const void *b)
156dc0093feschrock	const sym_entry_t *syma = a;
157dc0093feschrock	const sym_entry_t *symb = b;
158dc0093feschrock	const char *aname = syma->se_name;
159dc0093feschrock	const char *bname = symb->se_name;
161dc0093feschrock	if (syma->se_sym.st_value < symb->se_sym.st_value)
162dc0093feschrock		return (-1);
164dc0093feschrock	if (syma->se_sym.st_value > symb->se_sym.st_value)
165dc0093feschrock		return (1);
167dc0093feschrock	/*
168dc0093feschrock	 * Prefer functions over non-functions
169dc0093feschrock	 */
170dc0093feschrock	if (GELF_ST_TYPE(syma->se_sym.st_info) !=
171dc0093feschrock	    GELF_ST_TYPE(symb->se_sym.st_info)) {
172dc0093feschrock		if (GELF_ST_TYPE(syma->se_sym.st_info) == STT_FUNC)
173dc0093feschrock			return (-1);
174dc0093feschrock		if (GELF_ST_TYPE(symb->se_sym.st_info) == STT_FUNC)
175dc0093feschrock			return (1);
176dc0093feschrock	}
178dc0093feschrock	/*
179dc0093feschrock	 * For symbols with the same address and type, we sort them according to
180dc0093feschrock	 * a hierarchy:
181dc0093feschrock	 *
182dc0093feschrock	 * 	1. weak symbols (common name)
183dc0093feschrock	 * 	2. global symbols (external name)
184dc0093feschrock	 * 	3. local symbols
185dc0093feschrock	 */
186dc0093feschrock	if (GELF_ST_BIND(syma->se_sym.st_info) !=
187dc0093feschrock	    GELF_ST_BIND(symb->se_sym.st_info)) {
188dc0093feschrock		if (GELF_ST_BIND(syma->se_sym.st_info) == STB_WEAK)
189dc0093feschrock			return (-1);
190dc0093feschrock		if (GELF_ST_BIND(symb->se_sym.st_info) == STB_WEAK)
191dc0093feschrock			return (1);
193dc0093feschrock		if (GELF_ST_BIND(syma->se_sym.st_info) == STB_GLOBAL)
194dc0093feschrock			return (-1);
195dc0093feschrock		if (GELF_ST_BIND(symb->se_sym.st_info) == STB_GLOBAL)
196dc0093feschrock			return (1);
197dc0093feschrock	}
199dc0093feschrock	/*
200dc0093feschrock	 * As a last resort, if we have multiple symbols of the same type at the
201dc0093feschrock	 * same address, prefer the version with the fewest leading underscores.
202dc0093feschrock	 */
203dc0093feschrock	if (aname == NULL)
204dc0093feschrock		return (-1);
205dc0093feschrock	if (bname == NULL)
206dc0093feschrock		return (1);
208dc0093feschrock	while (*aname == '_' && *bname == '_') {
209dc0093feschrock		aname++;
210dc0093feschrock		bname++;
211dc0093feschrock	}
213dc0093feschrock	if (*bname == '_')
214dc0093feschrock		return (-1);
215dc0093feschrock	if (*aname == '_')
216dc0093feschrock		return (1);
218dc0093feschrock	/*
219dc0093feschrock	 * Prefer the symbol with the smaller size.
220dc0093feschrock	 */
221dc0093feschrock	if (syma->se_sym.st_size < symb->se_sym.st_size)
222dc0093feschrock		return (-1);
223dc0093feschrock	if (syma->se_sym.st_size > symb->se_sym.st_size)
224dc0093feschrock		return (1);
226dc0093feschrock	/*
227dc0093feschrock	 * We really do have two identical symbols for some reason.  Just report
228dc0093feschrock	 * them as equal, and to the lucky one go the spoils.
229dc0093feschrock	 */
230dc0093feschrock	return (0);
234dc0093feschrock * Construct an optimized symbol table sorted by starting address.
235dc0093feschrock */
236dc0093feschrockstatic void
237dc0093feschrockconstruct_symtab(dis_tgt_t *tgt)
239dc0093feschrock	Elf_Scn *scn;
240dc0093feschrock	GElf_Shdr shdr;
241dc0093feschrock	Elf_Data *symdata;
242dc0093feschrock	int i;
243dc0093feschrock	GElf_Word *symshndx = NULL;
244dc0093feschrock	int symshndx_size;
245dc0093feschrock	sym_entry_t *sym;
246dc0093feschrock	sym_entry_t *p_symtab = NULL;
247dc0093feschrock	int nsym = 0; /* count of symbols we're not interested in */
249dc0093feschrock	/*
250dc0093feschrock	 * Find the symshndx section, if any
251dc0093feschrock	 */
252dc0093feschrock	for (scn = elf_nextscn(tgt->dt_elf, NULL); scn != NULL;
253dc0093feschrock	    scn = elf_nextscn(tgt->dt_elf, scn)) {
254dc0093feschrock		if (gelf_getshdr(scn, &shdr) == NULL)
255dc0093feschrock			break;
256dc0093feschrock		if (shdr.sh_type == SHT_SYMTAB_SHNDX &&
257dc0093feschrock		    shdr.sh_link == tgt->dt_symidx) {
258dc0093feschrock			Elf_Data	*data;
260dc0093feschrock			if ((data = elf_getdata(scn, NULL)) != NULL) {
261dc0093feschrock				symshndx = (GElf_Word *)data->d_buf;
262dc0093feschrock				symshndx_size = data->d_size /
263dc0093feschrock				    sizeof (GElf_Word);
264dc0093feschrock				break;
265dc0093feschrock			}
266dc0093feschrock		}
267dc0093feschrock	}
269dc0093feschrock	if ((scn = elf_getscn(tgt->dt_elf, tgt->dt_symidx)) == NULL)
270dc0093feschrock		die("%s: failed to get section information", tgt->dt_filename);
271dc0093feschrock	if (gelf_getshdr(scn, &shdr) == NULL)
272dc0093feschrock		die("%s: failed to get section header", tgt->dt_filename);
273dc0093feschrock	if (shdr.sh_entsize == 0)
274dc0093feschrock		die("%s: symbol table has zero size", tgt->dt_filename);
276dc0093feschrock	if ((symdata = elf_getdata(scn, NULL)) == NULL)
277dc0093feschrock		die("%s: failed to get symbol table", tgt->dt_filename);
279dc0093feschrock	tgt->dt_symcount = symdata->d_size / gelf_fsize(tgt->dt_elf, ELF_T_SYM,
2807a65609jmcp	    1, EV_CURRENT);
282dc0093feschrock	p_symtab = safe_malloc(tgt->dt_symcount * sizeof (sym_entry_t));
284dc0093feschrock	for (i = 0, sym = p_symtab; i < tgt->dt_symcount; i++) {
285dc0093feschrock		if (gelf_getsym(symdata, i, &(sym->se_sym)) == NULL) {
286dc0093feschrock			warn("%s: gelf_getsym returned NULL for %d",
2877a65609jmcp			    tgt->dt_filename, i);
288dc0093feschrock			nsym++;
289dc0093feschrock			continue;
290dc0093feschrock		}
292dc0093feschrock		/*
293dc0093feschrock		 * We're only interested in data symbols.
294dc0093feschrock		 */
295dc0093feschrock		if (!IS_DATA_TYPE(GELF_ST_TYPE(sym->se_sym.st_info))) {
296dc0093feschrock			nsym++;
297dc0093feschrock			continue;
298dc0093feschrock		}
300dc0093feschrock		if (sym->se_sym.st_shndx == SHN_XINDEX && symshndx != NULL) {
301dc0093feschrock			if (i > symshndx_size) {
302dc0093feschrock				warn("%s: bad SHNX_XINDEX %d",
3037a65609jmcp				    tgt->dt_filename, i);
304dc0093feschrock				sym->se_shndx = -1;
305dc0093feschrock			} else {
306dc0093feschrock				sym->se_shndx = symshndx[i];
307dc0093feschrock			}
308dc0093feschrock		} else {
309dc0093feschrock			sym->se_shndx = sym->se_sym.st_shndx;
310dc0093feschrock		}
3126e6df3cJason King		/* Deal with symbols with special section indicies */
3136e6df3cJason King		if (sym->se_shndx == SHN_ABS) {
3146e6df3cJason King			/*
3156e6df3cJason King			 * If st_value == 0, references to these
3166e6df3cJason King			 * symbols in code are modified in situ
3176e6df3cJason King			 * thus we will never attempt to look
3186e6df3cJason King			 * them up.
3196e6df3cJason King			 */
3206e6df3cJason King			if (sym->se_sym.st_value == 0) {
3216e6df3cJason King				/*
3226e6df3cJason King				 * References to these symbols in code
3236e6df3cJason King				 * are modified in situ by the runtime
3246e6df3cJason King				 * linker and no code on disk will ever
3256e6df3cJason King				 * attempt to look them up.
3266e6df3cJason King				 */
3276e6df3cJason King				nsym++;
3286e6df3cJason King				continue;
3296e6df3cJason King			} else {
3306e6df3cJason King				/*
3316e6df3cJason King				 * If st_value != 0, (such as examining
3326e6df3cJason King				 * something in /system/object/.../object)
3336e6df3cJason King				 * the values should resolve to a value
3346e6df3cJason King				 * within an existing section (such as
3356e6df3cJason King				 * .data).  This also means it never needs
3366e6df3cJason King				 * to have st_value mapped.
3376e6df3cJason King				 */
3386e6df3cJason King				sym++;
3396e6df3cJason King				continue;
3406e6df3cJason King			}
3416e6df3cJason King		}
3426e6df3cJason King
3436e6df3cJason King		/*
3446e6df3cJason King		 * Ignore the symbol if it has some other special
3456e6df3cJason King		 * section index
3466e6df3cJason King		 */
3476e6df3cJason King		if (sym->se_shndx == SHN_UNDEF ||
3486e6df3cJason King		    sym->se_shndx >= SHN_LORESERVE) {
3496e6df3cJason King			nsym++;
3506e6df3cJason King			continue;
3516e6df3cJason King		}
3526e6df3cJason King
353dc0093feschrock		if ((sym->se_name = elf_strptr(tgt->dt_elf, shdr.sh_link,
354dc0093feschrock		    (size_t)sym->se_sym.st_name)) == NULL) {
355dc0093feschrock			warn("%s: failed to lookup symbol %d name",
3567a65609jmcp			    tgt->dt_filename, i);
357dc0093feschrock			nsym++;
358dc0093feschrock			continue;
359dc0093feschrock		}
361b5f3c6fJason King		/*
362b5f3c6fJason King		 * If we had to map this section, its symbol value
363b5f3c6fJason King		 * also needs to be mapped.
364b5f3c6fJason King		 */
365b5f3c6fJason King		if (tgt->dt_shnmap[sym->se_shndx].dm_mapped)
366b5f3c6fJason King			sym->se_sym.st_value +=
367b5f3c6fJason King			    tgt->dt_shnmap[sym->se_shndx].dm_start;
368b5f3c6fJason King
369dc0093feschrock		sym++;
370dc0093feschrock	}
372dc0093feschrock	tgt->dt_symcount -= nsym;
3737a65609jmcp	tgt->dt_symtab = realloc(p_symtab, tgt->dt_symcount *
3747a65609jmcp	    sizeof (sym_entry_t));
376dc0093feschrock	qsort(tgt->dt_symtab, tgt->dt_symcount, sizeof (sym_entry_t),
377dc0093feschrock	    sym_compare);
381b5f3c6fJason King * Assign virtual address ranges for sections that need it
382b5f3c6fJason King */
383b5f3c6fJason Kingstatic void
384b5f3c6fJason Kingcreate_addrmap(dis_tgt_t *tgt)
385b5f3c6fJason King{
386b5f3c6fJason King	uint64_t addr;
387b5f3c6fJason King	int i;
388b5f3c6fJason King
389b5f3c6fJason King	if (tgt->dt_shnmap == NULL)
390b5f3c6fJason King		return;
391b5f3c6fJason King
392b5f3c6fJason King	/* find the greatest used address */
393b5f3c6fJason King	for (addr = 0, i = 1; i < tgt->dt_shncount; i++)
394b5f3c6fJason King		if (tgt->dt_shnmap[i].dm_start > addr)
395b5f3c6fJason King			addr = tgt->dt_shnmap[i].dm_start +
396b5f3c6fJason King			    tgt->dt_shnmap[i].dm_length;
397b5f3c6fJason King
398b5f3c6fJason King	addr = P2ROUNDUP(addr, 0x1000);
399b5f3c6fJason King
400b5f3c6fJason King	/*
401b5f3c6fJason King	 * Assign section a starting address beyond the largest mapped section
402b5f3c6fJason King	 * if no address was given.
403b5f3c6fJason King	 */
404b5f3c6fJason King	for (i = 1; i < tgt->dt_shncount; i++) {
405b5f3c6fJason King		if (tgt->dt_shnmap[i].dm_start != 0)
406b5f3c6fJason King			continue;
407b5f3c6fJason King
408b5f3c6fJason King		tgt->dt_shnmap[i].dm_start = addr;
409b5f3c6fJason King		tgt->dt_shnmap[i].dm_mapped = B_TRUE;
410b5f3c6fJason King		addr = P2ROUNDUP(addr + tgt->dt_shnmap[i].dm_length, 0x1000);
411b5f3c6fJason King	}
412b5f3c6fJason King}
413b5f3c6fJason King
414b5f3c6fJason King/*
415dc0093feschrock * Create a target backed by an ELF file.
416dc0093feschrock */
417dc0093feschrockdis_tgt_t *
418dc0093feschrockdis_tgt_create(const char *file)
420dc0093feschrock	dis_tgt_t *tgt, *current;
421dc0093feschrock	int idx;
422dc0093feschrock	Elf *elf;
423dc0093feschrock	GElf_Ehdr ehdr;
424dc0093feschrock	Elf_Arhdr *arhdr = NULL;
425dc0093feschrock	int cmd;
427dc0093feschrock	if (elf_version(EV_CURRENT) == EV_NONE)
428dc0093feschrock		die("libelf(3ELF) out of date");
430dc0093feschrock	tgt = safe_malloc(sizeof (dis_tgt_t));
432dc0093feschrock	if ((tgt->dt_fd = open(file, O_RDONLY)) < 0) {
433dc0093feschrock		warn("%s: failed opening file, reason: %s", file,
4347a65609jmcp		    strerror(errno));
435dc0093feschrock		free(tgt);
436dc0093feschrock		return (NULL);
437dc0093feschrock	}
439dc0093feschrock	if ((tgt->dt_elf_root =
440dc0093feschrock	    elf_begin(tgt->dt_fd, ELF_C_READ, NULL)) == NULL) {
441dc0093feschrock		warn("%s: invalid or corrupt ELF file", file);
442dc0093feschrock		dis_tgt_destroy(tgt);
443dc0093feschrock		return (NULL);
444dc0093feschrock	}
446dc0093feschrock	current = tgt;
447dc0093feschrock	cmd = ELF_C_READ;
448dc0093feschrock	while ((elf = elf_begin(tgt->dt_fd, cmd, tgt->dt_elf_root)) != NULL) {
44927553b5Richard Lowe		size_t shnum = 0;
451dc0093feschrock		if (elf_kind(tgt->dt_elf_root) == ELF_K_AR &&
452dc0093feschrock		    (arhdr = elf_getarhdr(elf)) == NULL) {
453dc0093feschrock			warn("%s: malformed archive", file);
454dc0093feschrock			dis_tgt_destroy(tgt);
455dc0093feschrock			return (NULL);
456dc0093feschrock		}
458dc0093feschrock		/*
459dc0093feschrock		 * Make sure that this Elf file is sane
460dc0093feschrock		 */
461dc0093feschrock		if (gelf_getehdr(elf, &ehdr) == NULL) {
462dc0093feschrock			if (arhdr != NULL) {
463dc0093feschrock				/*
464dc0093feschrock				 * For archives, we drive on in the face of bad
465dc0093feschrock				 * members.  The "/" and "//" members are
466dc0093feschrock				 * special, and should be silently ignored.
467dc0093feschrock				 */
468dc0093feschrock				if (strcmp(arhdr->ar_name, "/") != 0 &&
469dc0093feschrock				    strcmp(arhdr->ar_name, "//") != 0)
470dc0093feschrock					warn("%s[%s]: invalid file type",
471dc0093feschrock					    file, arhdr->ar_name);
472dc0093feschrock				cmd = elf_next(elf);
473dc0093feschrock				(void) elf_end(elf);
474dc0093feschrock				continue;
475dc0093feschrock			}
477dc0093feschrock			warn("%s: invalid file type", file);
478dc0093feschrock			dis_tgt_destroy(tgt);
479dc0093feschrock			return (NULL);
480dc0093feschrock		}
482dc0093feschrock		/*
483dc0093feschrock		 * If we're seeing a new Elf object, then we have an