kextsymboltool.c   [plain text]


/*
 * Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_LICENSE_HEADER_END@
 */
#include <libc.h>
#include <errno.h>
#include <ctype.h>

#include <mach/mach_init.h>

#include <sys/stat.h>
#include <sys/file.h>
#include <sys/mman.h>

#include <mach-o/arch.h>
#include <mach-o/fat.h>
#include <mach-o/loader.h>
#include <mach-o/nlist.h>
#include <mach-o/swap.h>

#include <uuid/uuid.h>
#include <stdbool.h>

#pragma mark Typedefs, Enums, Constants
/*********************************************************************
* Typedefs, Enums, Constants
*********************************************************************/
typedef enum {
	kErrorNone = 0,
	kError,
	kErrorFileAccess,
	kErrorDiskFull,
	kErrorDuplicate
} ToolError;

#pragma mark Function Protos
/*********************************************************************
* Function Protos
*********************************************************************/
__private_extern__ ToolError
readFile(const char *path, vm_offset_t * objAddr, vm_size_t * objSize);

__private_extern__ ToolError
writeFile(int fd, const void * data, size_t length);

__private_extern__ ToolError
seekFile(int fd, off_t offset);

extern char* __cxa_demangle(const char* mangled_name,
    char* buf,
    size_t* n,
    int* status);

#pragma mark Functions
/*********************************************************************
*********************************************************************/
__private_extern__ ToolError
writeFile(int fd, const void * data, size_t length)
{
	ToolError err;

	if (length != (size_t)write(fd, data, length)) {
		err = kErrorDiskFull;
	} else {
		err = kErrorNone;
	}

	if (kErrorNone != err) {
		perror("couldn't write output");
	}

	return err;
}

/*********************************************************************
*********************************************************************/
__private_extern__ ToolError
seekFile(int fd, off_t offset)
{
	ToolError err;

	if (offset != lseek(fd, offset, SEEK_SET)) {
		err = kErrorDiskFull;
	} else {
		err = kErrorNone;
	}

	if (kErrorNone != err) {
		perror("couldn't write output");
	}

	return err;
}

/*********************************************************************
*********************************************************************/
__private_extern__ ToolError
readFile(const char *path, vm_offset_t * objAddr, vm_size_t * objSize)
{
	ToolError err = kErrorFileAccess;
	int fd;
	struct stat stat_buf;

	*objAddr = 0;
	*objSize = 0;

	do{
		if ((fd = open(path, O_RDONLY)) == -1) {
			continue;
		}

		if (fstat(fd, &stat_buf) == -1) {
			continue;
		}

		if (0 == (stat_buf.st_mode & S_IFREG)) {
			continue;
		}

		/* Don't try to map an empty file, it fails now due to conformance
		 * stuff (PR 4611502).
		 */
		if (0 == stat_buf.st_size) {
			err = kErrorNone;
			continue;
		}

		*objSize = stat_buf.st_size;

		*objAddr = (vm_offset_t)mmap(NULL /* address */, *objSize,
		    PROT_READ | PROT_WRITE, MAP_FILE | MAP_PRIVATE /* flags */,
		    fd, 0 /* offset */);

		if ((void *)*objAddr == MAP_FAILED) {
			*objAddr = 0;
			*objSize = 0;
			continue;
		}

		err = kErrorNone;
	} while (false);

	if (-1 != fd) {
		close(fd);
	}
	if (kErrorNone != err) {
		fprintf(stderr, "couldn't read %s: %s\n", path, strerror(errno));
	}

	return err;
}


enum { kExported = 0x00000001, kObsolete = 0x00000002 };

struct symbol {
	char * name;
	unsigned int name_len;
	char * indirect;
	unsigned int indirect_len;
	unsigned int flags;
	struct symbol * list;
	unsigned int list_count;
};

static bool
issymchar( char c )
{
	return (c > ' ') && (c <= '~') && (c != ':') && (c != '#');
}

static bool
iswhitespace( char c )
{
	return (c == ' ') || (c == '\t');
}

/*
 * Function for qsort for comparing symbol list names.
 */
static int
qsort_cmp(const void * _left, const void * _right)
{
	struct symbol * left  = (struct symbol *) _left;
	struct symbol * right = (struct symbol *) _right;

	return strcmp(left->name, right->name);
}

/*
 * Function for bsearch for finding a symbol name.
 */

static int
bsearch_cmp( const void * _key, const void * _cmp)
{
	char * key = (char *)_key;
	struct symbol * cmp = (struct symbol *) _cmp;

	return strcmp(key, cmp->name);
}

struct bsearch_key {
	char * name;
	unsigned int name_len;
};

static int
bsearch_cmp_prefix( const void * _key, const void * _cmp)
{
	struct bsearch_key * key = (struct bsearch_key *)_key;
	struct symbol *      cmp = (struct symbol *) _cmp;

	return strncmp(key->name, cmp->name, key->name_len);
}

static uint32_t
count_symbols(char * file, vm_size_t file_size)
{
	uint32_t nsyms = 0;
	char *   scan;
	char *   eol;
	char *   next;

	for (scan = file; true; scan = next) {
		eol = memchr(scan, '\n', file_size - (scan - file));
		if (eol == NULL) {
			break;
		}
		next = eol + 1;

		/* Skip empty lines.
		 */
		if (eol == scan) {
			continue;
		}

		/* Skip comment lines.
		 */
		if (scan[0] == '#') {
			continue;
		}

		/* Scan past any non-symbol characters at the beginning of the line. */
		while ((scan < eol) && !issymchar(*scan)) {
			scan++;
		}

		/* No symbol on line? Move along.
		 */
		if (scan == eol) {
			continue;
		}

		/* Skip symbols starting with '.'.
		 */
		if (scan[0] == '.') {
			continue;
		}
		nsyms++;
	}

	return nsyms;
}

static uint32_t
store_symbols(char * file, vm_size_t file_size, struct symbol * symbols, uint32_t idx, uint32_t max_symbols)
{
	char *   scan;
	char *   line;
	char *   eol;
	char *   next;

	uint32_t strtabsize;

	strtabsize = 0;

	for (scan = file, line = file; true; scan = next, line = next) {
		char *       name = NULL;
		char *       name_term = NULL;
		unsigned int name_len = 0;
		char *       indirect = NULL;
		char *       indirect_term = NULL;
		unsigned int indirect_len = 0;
		char *       option = NULL;
		char *       option_term = NULL;
		unsigned int option_len = 0;
		char         optionstr[256];
		boolean_t    obsolete = 0;

		eol = memchr(scan, '\n', file_size - (scan - file));
		if (eol == NULL) {
			break;
		}
		next = eol + 1;

		/* Skip empty lines.
		 */
		if (eol == scan) {
			continue;
		}

		*eol = '\0';

		/* Skip comment lines.
		 */
		if (scan[0] == '#') {
			continue;
		}

		/* Scan past any non-symbol characters at the beginning of the line. */
		while ((scan < eol) && !issymchar(*scan)) {
			scan++;
		}

		/* No symbol on line? Move along.
		 */
		if (scan == eol) {
			continue;
		}

		/* Skip symbols starting with '.'.
		 */
		if (scan[0] == '.') {
			continue;
		}

		name = scan;

		/* Find the end of the symbol.
		 */
		while ((*scan != '\0') && issymchar(*scan)) {
			scan++;
		}

		/* Note char past end of symbol.
		 */
		name_term = scan;

		/* Stored length must include the terminating nul char.
		 */
		name_len = name_term - name + 1;

		/* Now look for an indirect.
		 */
		if (*scan != '\0') {
			while ((*scan != '\0') && iswhitespace(*scan)) {
				scan++;
			}
			if (*scan == ':') {
				scan++;
				while ((*scan != '\0') && iswhitespace(*scan)) {
					scan++;
				}
				if (issymchar(*scan)) {
					indirect = scan;

					/* Find the end of the symbol.
					 */
					while ((*scan != '\0') && issymchar(*scan)) {
						scan++;
					}

					/* Note char past end of symbol.
					 */
					indirect_term = scan;

					/* Stored length must include the terminating nul char.
					 */
					indirect_len = indirect_term - indirect + 1;
				} else if (*scan == '\0') {
					fprintf(stderr, "bad format in symbol line: %s\n", line);
					exit(1);
				}
			} else if (*scan != '\0' && *scan != '-') {
				fprintf(stderr, "bad format in symbol line: %s\n", line);
				exit(1);
			}
		}

		/* Look for options.
		 */
		if (*scan != '\0') {
			while ((*scan != '\0') && iswhitespace(*scan)) {
				scan++;
			}

			if (*scan == '-') {
				scan++;

				if (isalpha(*scan)) {
					option = scan;

					/* Find the end of the option.
					 */
					while ((*scan != '\0') && isalpha(*scan)) {
						scan++;
					}

					/* Note char past end of option.
					 */
					option_term = scan;
					option_len = option_term - option;

					if (option_len >= sizeof(optionstr)) {
						fprintf(stderr, "option too long in symbol line: %s\n", line);
						exit(1);
					}
					memcpy(optionstr, option, option_len);
					optionstr[option_len] = '\0';

					/* Find the option.
					 */
					if (!strncmp(optionstr, "obsolete", option_len)) {
						obsolete = TRUE;
					}
				} else if (*scan == '\0') {
					fprintf(stderr, "bad format in symbol line: %s\n", line);
					exit(1);
				}
			}
		}

		if (idx >= max_symbols) {
			fprintf(stderr, "symbol[%d/%d] overflow: %s\n", idx, max_symbols, line);
			exit(1);
		}

		*name_term = '\0';
		if (indirect_term) {
			*indirect_term = '\0';
		}

		symbols[idx].name = name;
		symbols[idx].name_len = name_len;
		symbols[idx].indirect = indirect;
		symbols[idx].indirect_len = indirect_len;
		symbols[idx].flags = (obsolete) ? kObsolete : 0;

		strtabsize += symbols[idx].name_len + symbols[idx].indirect_len;
		idx++;
	}

	return strtabsize;
}

static const NXArchInfo *
lookup_arch(const char *archstring)
{
	/*
	 * As new architectures are supported by xnu, add a mapping function
	 * without relying on host libraries.
	 */
	static const NXArchInfo archlist[] = {
		{ "x86_64", 0x01000007 /* CPU_TYPE_X86_64 */, 3 /* CPU_SUBTYPE_X86_64_ALL */, NX_LittleEndian, NULL },
		{ "x86_64h", 0x01000007 /* CPU_TYPE_X86_64 */, 8 /* CPU_SUBTYPE_X86_64_H */, NX_LittleEndian, NULL },
		{ "armv7", 12 /* CPU_TYPE_ARM */, 9 /* CPU_SUBTYPE_ARM_V7 */, NX_LittleEndian, NULL },
		{ "armv7s", 12 /* CPU_TYPE_ARM */, 11 /* CPU_SUBTYPE_ARM_V7S */, NX_LittleEndian, NULL },
		{ "armv7k", 12 /* CPU_TYPE_ARM */, 12 /* CPU_SUBTYPE_ARM_V7K */, NX_LittleEndian, NULL },
		{ "arm64", 0x0100000c /* CPU_TYPE_ARM64 */, 0 /* CPU_SUBTYPE_ARM64_ALL */, NX_LittleEndian, NULL },
	};
	unsigned long i;

	for (i = 0; i < sizeof(archlist) / sizeof(archlist[0]); i++) {
		if (0 == strcmp(archstring, archlist[i].name)) {
			return &archlist[i];
		}
	}

	return NULL;
}

/*********************************************************************
*********************************************************************/
int
main(int argc, char * argv[])
{
	ToolError   err;
	int                 i, fd;
	const char *        output_name = NULL;
	uint32_t            zero = 0, num_files = 0;
	uint32_t            filenum;
	uint32_t            strx, strtabsize, strtabpad;
	struct symbol *     import_symbols;
	struct symbol *     export_symbols;
	uint32_t            num_import_syms, num_export_syms;
	uint32_t            result_count, num_removed_syms;
	uint32_t            import_idx, export_idx;
	const NXArchInfo *  host_arch;
	const NXArchInfo *  target_arch;
	boolean_t           require_imports = true;
	boolean_t           diff = false;


	struct file {
		vm_offset_t  mapped;
		vm_size_t    mapped_size;
		uint32_t     nsyms;
		boolean_t    import;
		const char * path;
	};
	struct file files[64];

	host_arch = NXGetLocalArchInfo();
	target_arch = host_arch;

	for (i = 1; i < argc; i += 2) {
		boolean_t import;

		if (!strcmp("-sect", argv[i])) {
			require_imports = false;
			i--;
			continue;
		}
		if (!strcmp("-diff", argv[i])) {
			require_imports = false;
			diff = true;
			i--;
			continue;
		}

		if (i == (argc - 1)) {
			fprintf(stderr, "bad arguments: %s\n", argv[i]);
			exit(1);
		}

		if (!strcmp("-arch", argv[i])) {
			target_arch = lookup_arch(argv[i + 1]);
			if (!target_arch) {
				fprintf(stderr, "unknown architecture name: %s\n", argv[i + 1]);
				exit(1);
			}
			continue;
		}
		if (!strcmp("-output", argv[i])) {
			output_name = argv[i + 1];
			continue;
		}

		if (!strcmp("-import", argv[i])) {
			import = true;
		} else if (!strcmp("-export", argv[i])) {
			import = false;
		} else {
			fprintf(stderr, "unknown option: %s\n", argv[i]);
			exit(1);
		}

		err = readFile(argv[i + 1], &files[num_files].mapped, &files[num_files].mapped_size);
		if (kErrorNone != err) {
			exit(1);
		}

		if (files[num_files].mapped && files[num_files].mapped_size) {
			files[num_files].import = import;
			files[num_files].path   = argv[i + 1];
			num_files++;
		}
	}

	if (!output_name) {
		fprintf(stderr, "no output file\n");
		exit(1);
	}

	num_import_syms = 0;
	num_export_syms = 0;
	for (filenum = 0; filenum < num_files; filenum++) {
		files[filenum].nsyms = count_symbols((char *) files[filenum].mapped, files[filenum].mapped_size);
		if (files[filenum].import) {
			num_import_syms += files[filenum].nsyms;
		} else {
			num_export_syms += files[filenum].nsyms;
		}
	}

	import_symbols = calloc(num_import_syms, sizeof(struct symbol));
	export_symbols = calloc(num_export_syms, sizeof(struct symbol));

	import_idx = 0;
	export_idx = 0;

	for (filenum = 0; filenum < num_files; filenum++) {
		if (files[filenum].import) {
			store_symbols((char *) files[filenum].mapped, files[filenum].mapped_size,
			    import_symbols, import_idx, num_import_syms);
			import_idx += files[filenum].nsyms;
		} else {
			store_symbols((char *) files[filenum].mapped, files[filenum].mapped_size,
			    export_symbols, export_idx, num_export_syms);
			export_idx += files[filenum].nsyms;
		}
		if (false && !files[filenum].nsyms) {
			fprintf(stderr, "warning: file %s contains no names\n", files[filenum].path);
		}
	}


	qsort(import_symbols, num_import_syms, sizeof(struct symbol), &qsort_cmp);
	qsort(export_symbols, num_export_syms, sizeof(struct symbol), &qsort_cmp);

	result_count = 0;
	num_removed_syms = 0;
	strtabsize = 4;
	if (num_import_syms) {
		for (export_idx = 0; export_idx < num_export_syms; export_idx++) {
			struct symbol * result;
			char * name;
			size_t len;
			boolean_t wild;

			name = export_symbols[export_idx].indirect;
			len  = export_symbols[export_idx].indirect_len;
			if (!name) {
				name = export_symbols[export_idx].name;
				len  = export_symbols[export_idx].name_len;
			}
			wild = ((len > 2) && ('*' == name[len -= 2]));
			if (wild) {
				struct bsearch_key key;
				key.name = name;
				key.name_len = len;
				result = bsearch(&key, import_symbols,
				    num_import_syms, sizeof(struct symbol), &bsearch_cmp_prefix);

				if (result) {
					struct symbol * first;
					struct symbol * last;

					strtabsize += (result->name_len + result->indirect_len);

					first = result;
					while (--first >= &import_symbols[0]) {
						if (bsearch_cmp_prefix(&key, first)) {
							break;
						}
						strtabsize += (first->name_len + first->indirect_len);
					}
					first++;

					last = result;
					while (++last < (&import_symbols[0] + num_import_syms)) {
						if (bsearch_cmp_prefix(&key, last)) {
							break;
						}
						strtabsize += (last->name_len + last->indirect_len);
					}
					result_count += last - first;
					result = first;
					export_symbols[export_idx].list = first;
					export_symbols[export_idx].list_count = last - first;
					export_symbols[export_idx].flags |= kExported;
				}
			} else {
				result = bsearch(name, import_symbols,
				    num_import_syms, sizeof(struct symbol), &bsearch_cmp);
			}

			if (!result && require_imports) {
				int status;
				char * demangled_result =
				    __cxa_demangle(export_symbols[export_idx].name + 1, NULL, NULL, &status);
				fprintf(stderr, "exported name not in import list: %s\n",
				    demangled_result ? demangled_result : export_symbols[export_idx].name);
//		fprintf(stderr, "                                : %s\n", export_symbols[export_idx].name);
				if (demangled_result) {
					free(demangled_result);
				}
				num_removed_syms++;
			}
			if (diff) {
				if (!result) {
					result = &export_symbols[export_idx];
				} else {
					result = NULL;
				}
			}
			if (result && !wild) {
				export_symbols[export_idx].flags |= kExported;
				strtabsize += (export_symbols[export_idx].name_len + export_symbols[export_idx].indirect_len);
				result_count++;
				export_symbols[export_idx].list = &export_symbols[export_idx];
				export_symbols[export_idx].list_count = 1;
			}
		}
	}
	strtabpad = (strtabsize + 3) & ~3;

	if (require_imports && num_removed_syms) {
		err = kError;
		goto finish;
	}

	fd = open(output_name, O_WRONLY | O_CREAT | O_TRUNC, 0755);
	if (-1 == fd) {
		perror("couldn't write output");
		err = kErrorFileAccess;
		goto finish;
	}

	struct symtab_command symcmd;
	struct uuid_command uuidcmd;
	off_t  symsoffset;

	symcmd.cmd          = LC_SYMTAB;
	symcmd.cmdsize      = sizeof(symcmd);
	symcmd.nsyms        = result_count;
	symcmd.strsize      = strtabpad;

	uuidcmd.cmd         = LC_UUID;
	uuidcmd.cmdsize     = sizeof(uuidcmd);
	uuid_generate(uuidcmd.uuid);

	if (CPU_ARCH_ABI64 & target_arch->cputype) {
		struct mach_header_64     hdr;
		struct segment_command_64 segcmd;

		hdr.magic       = MH_MAGIC_64;
		hdr.cputype     = target_arch->cputype;
		hdr.cpusubtype  = target_arch->cpusubtype;
		hdr.filetype    = MH_KEXT_BUNDLE;
		hdr.ncmds       = 3;
		hdr.sizeofcmds  = sizeof(segcmd) + sizeof(symcmd) + sizeof(uuidcmd);
		hdr.flags       = MH_INCRLINK;
		symsoffset      = mach_vm_round_page(hdr.sizeofcmds);

		segcmd.cmd      = LC_SEGMENT_64;
		segcmd.cmdsize  = sizeof(segcmd);
		strncpy(segcmd.segname, SEG_LINKEDIT, sizeof(segcmd.segname));
		segcmd.vmaddr   = 0;
		segcmd.vmsize   = result_count * sizeof(struct nlist_64) + strtabpad;
		segcmd.fileoff  = symsoffset;
		segcmd.filesize = segcmd.vmsize;
		segcmd.maxprot  = PROT_READ;
		segcmd.initprot = PROT_READ;
		segcmd.nsects   = 0;
		segcmd.flags    = SG_NORELOC;

		symcmd.symoff   = symsoffset;
		symcmd.stroff   = result_count * sizeof(struct nlist_64)
		    + symcmd.symoff;

		if (target_arch->byteorder != host_arch->byteorder) {
			swap_mach_header_64(&hdr, target_arch->byteorder);
			swap_segment_command_64(&segcmd, target_arch->byteorder);
		}
		err = writeFile(fd, &hdr, sizeof(hdr));
		if (kErrorNone != err) {
			goto finish;
		}
		err = writeFile(fd, &segcmd, sizeof(segcmd));
	} else {
		struct mach_header     hdr;
		struct segment_command segcmd;

		hdr.magic       = MH_MAGIC;
		hdr.cputype     = target_arch->cputype;
		hdr.cpusubtype  = target_arch->cpusubtype;
		hdr.filetype    = MH_KEXT_BUNDLE;
		hdr.ncmds       = 3;
		hdr.sizeofcmds  = sizeof(segcmd) + sizeof(symcmd) + sizeof(uuidcmd);
		hdr.flags       = MH_INCRLINK;
		symsoffset      = mach_vm_round_page(hdr.sizeofcmds);

		segcmd.cmd      = LC_SEGMENT;
		segcmd.cmdsize  = sizeof(segcmd);
		strncpy(segcmd.segname, SEG_LINKEDIT, sizeof(segcmd.segname));
		segcmd.vmaddr   = 0;
		segcmd.vmsize   = result_count * sizeof(struct nlist) + strtabpad;
		segcmd.fileoff  = symsoffset;
		segcmd.filesize = segcmd.vmsize;
		segcmd.maxprot  = PROT_READ;
		segcmd.initprot = PROT_READ;
		segcmd.nsects   = 0;
		segcmd.flags    = SG_NORELOC;

		symcmd.symoff   = symsoffset;
		symcmd.stroff   = result_count * sizeof(struct nlist)
		    + symcmd.symoff;

		if (target_arch->byteorder != host_arch->byteorder) {
			swap_mach_header(&hdr, target_arch->byteorder);
			swap_segment_command(&segcmd, target_arch->byteorder);
		}
		err = writeFile(fd, &hdr, sizeof(hdr));
		if (kErrorNone != err) {
			goto finish;
		}
		err = writeFile(fd, &segcmd, sizeof(segcmd));
	}

	if (kErrorNone != err) {
		goto finish;
	}

	if (target_arch->byteorder != host_arch->byteorder) {
		swap_symtab_command(&symcmd, target_arch->byteorder);
		swap_uuid_command(&uuidcmd, target_arch->byteorder);
	}
	err = writeFile(fd, &symcmd, sizeof(symcmd));
	if (kErrorNone != err) {
		goto finish;
	}
	err = writeFile(fd, &uuidcmd, sizeof(uuidcmd));
	if (kErrorNone != err) {
		goto finish;
	}

	err = seekFile(fd, symsoffset);
	if (kErrorNone != err) {
		goto finish;
	}

	strx = 4;
	for (export_idx = 0; export_idx < num_export_syms; export_idx++) {
		if (!export_symbols[export_idx].name) {
			continue;
		}
		if (!(kExported & export_symbols[export_idx].flags)) {
			continue;
		}

		if (export_idx
		    && export_symbols[export_idx - 1].name
		    && !strcmp(export_symbols[export_idx - 1].name, export_symbols[export_idx].name)) {
			fprintf(stderr, "duplicate export: %s\n", export_symbols[export_idx - 1].name);
			err = kErrorDuplicate;
			goto finish;
		}

		for (import_idx = 0; import_idx < export_symbols[export_idx].list_count; import_idx++) {
			if (export_symbols[export_idx].list != &export_symbols[export_idx]) {
				printf("wild: %s, %s\n", export_symbols[export_idx].name,
				    export_symbols[export_idx].list[import_idx].name);
			}
			if (CPU_ARCH_ABI64 & target_arch->cputype) {
				struct nlist_64 nl;

				nl.n_sect  = 0;
				nl.n_desc  = 0;
				nl.n_un.n_strx = strx;
				strx += export_symbols[export_idx].list[import_idx].name_len;

				if (export_symbols[export_idx].flags & kObsolete) {
					nl.n_desc |= N_DESC_DISCARDED;
				}

				if (export_symbols[export_idx].list[import_idx].indirect) {
					nl.n_type  = N_INDR | N_EXT;
					nl.n_value = strx;
					strx += export_symbols[export_idx].list[import_idx].indirect_len;
				} else {
					nl.n_type  = N_UNDF | N_EXT;
					nl.n_value = 0;
				}

				if (target_arch->byteorder != host_arch->byteorder) {
					swap_nlist_64(&nl, 1, target_arch->byteorder);
				}

				err = writeFile(fd, &nl, sizeof(nl));
			} else {
				struct nlist nl;

				nl.n_sect  = 0;
				nl.n_desc  = 0;
				nl.n_un.n_strx = strx;
				strx += export_symbols[export_idx].list[import_idx].name_len;

				if (export_symbols[export_idx].flags & kObsolete) {
					nl.n_desc |= N_DESC_DISCARDED;
				}

				if (export_symbols[export_idx].list[import_idx].indirect) {
					nl.n_type  = N_INDR | N_EXT;
					nl.n_value = strx;
					strx += export_symbols[export_idx].list[import_idx].indirect_len;
				} else {
					nl.n_type  = N_UNDF | N_EXT;
					nl.n_value = 0;
				}

				if (target_arch->byteorder != host_arch->byteorder) {
					swap_nlist(&nl, 1, target_arch->byteorder);
				}

				err = writeFile(fd, &nl, sizeof(nl));
			}
		}

		if (kErrorNone != err) {
			goto finish;
		}
	}

	strx = sizeof(uint32_t);
	err = writeFile(fd, &zero, strx);
	if (kErrorNone != err) {
		goto finish;
	}

	for (export_idx = 0; export_idx < num_export_syms; export_idx++) {
		if (!export_symbols[export_idx].name) {
			continue;
		}

		for (import_idx = 0; import_idx < export_symbols[export_idx].list_count; import_idx++) {
			err = writeFile(fd, export_symbols[export_idx].list[import_idx].name,
			    export_symbols[export_idx].list[import_idx].name_len);
			if (kErrorNone != err) {
				goto finish;
			}
			if (export_symbols[export_idx].list[import_idx].indirect) {
				err = writeFile(fd, export_symbols[export_idx].list[import_idx].indirect,
				    export_symbols[export_idx].list[import_idx].indirect_len);
				if (kErrorNone != err) {
					goto finish;
				}
			}
		}
	}

	err = writeFile(fd, &zero, strtabpad - strtabsize);
	if (kErrorNone != err) {
		goto finish;
	}

	close(fd);


finish:
	for (filenum = 0; filenum < num_files; filenum++) {
		// unmap file
		if (files[filenum].mapped_size) {
			munmap((caddr_t)files[filenum].mapped, files[filenum].mapped_size);
			files[filenum].mapped     = 0;
			files[filenum].mapped_size = 0;
		}
	}

	if (kErrorNone != err) {
		if (output_name && strncmp(output_name, "/dev/", 5)) {
			unlink(output_name);
		}
		exit(1);
	} else {
		exit(0);
	}
	return 0;
}