From 9ddb3ae9499634c2ec61b09d6358d4dc7ddf9f32 Mon Sep 17 00:00:00 2001 From: waylonis Date: Tue, 19 Dec 2006 20:28:32 +0000 Subject: [PATCH] Add Mac dump_syms tool and support class. Fixes issue #73. git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@94 4c0a9323-5329-0410-9bdc-e9ce6186880e --- src/common/mac/dump_syms.h | 67 ++ src/common/mac/dump_syms.m | 808 ++++++++++++++++++ .../dump_syms.xcodeproj/project.pbxproj | 215 +++++ src/tools/mac/dump_syms/dump_syms_tool.m | 139 +++ 4 files changed, 1229 insertions(+) create mode 100644 src/common/mac/dump_syms.h create mode 100644 src/common/mac/dump_syms.m create mode 100644 src/tools/mac/dump_syms/dump_syms.xcodeproj/project.pbxproj create mode 100644 src/tools/mac/dump_syms/dump_syms_tool.m diff --git a/src/common/mac/dump_syms.h b/src/common/mac/dump_syms.h new file mode 100644 index 00000000..38c7d3ce --- /dev/null +++ b/src/common/mac/dump_syms.h @@ -0,0 +1,67 @@ +// Copyright (c) 2006, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// dump_syms.h: Interface for DumpSymbols. This class will take a mach-o file +// and extract the symbol information and write it to a file using the +// airbag symbol file format. +// NOTE: Only Stabs format is currently supported -- not DWARF. + +#import + +@interface DumpSymbols : NSObject { + @protected + NSString *sourcePath_; // Source of symbols (STRONG) + NSString *architecture_; // Architecture to extract (STRONG) + NSString *uuidStr_; // Module UUID (STRONG) + NSMutableDictionary *addresses_; // Addresses and symbols (STRONG) + NSMutableDictionary *sources_; // Address and Source file paths (STRONG) + NSMutableArray *cppAddresses_; // Addresses of C++ symbols (STRONG) + NSMutableDictionary *headers_; // Mach-o header information (STRONG) + NSNumber *lastFunctionStart_; // Address of last function symbol parsed +} + +- (id)initWithContentsOfFile:(NSString *)machoFile; + +- (NSArray *)availableArchitectures; + +// One of ppc, x86, i386, ppc64, x86_64 +// If the architecture is not available, it will return NO +// If not set, the native architecture will be used +- (BOOL)setArchitecture:(NSString *)architecture; +- (NSString *)architecture; + +// Specify a UUID to output with the symbol file. +// If none specified, a new one will be created +- (void)setUUID:(NSString *)uuidStr; +- (NSString *)uuid; + +// Write the symbols to |symbolFilePath|. Return YES if successful. +- (BOOL)writeSymbolFile:(NSString *)symbolFilePath; + +@end diff --git a/src/common/mac/dump_syms.m b/src/common/mac/dump_syms.m new file mode 100644 index 00000000..d133e120 --- /dev/null +++ b/src/common/mac/dump_syms.m @@ -0,0 +1,808 @@ +// +// Copyright (c) 2006, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// dump_syms.m: Create a symbol file for use with minidumps + +#include +#include + +#include +#include +#include +#include +#include +#include + +#import + +#import "dump_syms.h" + +static NSString *kAddressSymbolKey = @"symbol"; +static NSString *kAddressConvertedSymbolKey = @"converted_symbol"; +static NSString *kAddressSourceLineKey = @"line"; +static NSString *kFunctionSizeKey = @"size"; +static NSString *kHeaderBaseAddressKey = @"baseAddr"; +static NSString *kHeaderSizeKey = @"size"; +static NSString *kHeaderOffsetKey = @"offset"; // Offset to the header +static NSString *kHeaderIs64BitKey = @"is64"; +static NSString *kUnknownSymbol = @"???"; + +@interface DumpSymbols(PrivateMethods) +- (NSString *)stringFromTask:(NSString *)action args:(NSArray *)args + standardIn:(NSFileHandle *)standardIn; +- (NSArray *)convertCPlusPlusSymbols:(NSArray *)symbols; +- (void)convertSymbols; +- (void)addFunction:(NSString *)name line:(int)line address:(uint64_t)address; +- (BOOL)processSymbolItem:(struct nlist_64 *)list stringTable:(char *)table; +- (BOOL)loadSymbolInfo:(void *)base offset:(uint32_t)offset; +- (BOOL)loadSymbolInfo64:(void *)base offset:(uint32_t)offset; +- (BOOL)loadSymbolInfoForArchitecture; +- (BOOL)loadHeader:(void *)base offset:(uint32_t)offset; +- (BOOL)loadHeader64:(void *)base offset:(uint32_t)offset; +- (BOOL)loadModuleInfo; +- (NSMutableString *)generateSymbolFileString; +@end + +@implementation DumpSymbols +//============================================================================= +- (NSString *)stringFromTask:(NSString *)action args:(NSArray *)args + standardIn:(NSFileHandle *)standardIn { + NSTask *task = [[NSTask alloc] init]; + [task setLaunchPath:action]; + NSPipe *pipe = [NSPipe pipe]; + [task setStandardOutput:pipe]; + NSFileHandle *output = [pipe fileHandleForReading]; + + if (standardIn) + [task setStandardInput:standardIn]; + + if (args) + [task setArguments:args]; + + [task launch]; + + // This seems a bit strange, but when using [task waitUntilExit], it hangs + // waiting for data, but there isn't any. So, we'll poll for data, + // take a short nap, and then ask again + BOOL done = NO; + NSMutableData *allData = [NSMutableData data]; + NSData *data = nil; + int exceptionCount = 0; + + while (!done) { + data = nil; + // If there's a communications problem with the task, this might throw + // an exception. We'll catch and keep trying. + @try { + data = [output availableData]; + } + @catch (NSException *e) { + ++exceptionCount; + } + + [allData appendData:data]; + + // Loop over the data until we're no longer returning data. If we're + // still receiving data, sleep for 1/2 second and let the task + // continue. If we keep receiving exceptions, bail out + if (![data length] && data || exceptionCount > 10) + done = YES; + else + usleep(500); + } + + // Gather any remaining data + [task waitUntilExit]; + data = [output availableData]; + [allData appendData:data]; + [task release]; + + return [[[NSString alloc] initWithData:allData + encoding:NSUTF8StringEncoding] autorelease]; +} + +//============================================================================= +- (NSArray *)convertCPlusPlusSymbols:(NSArray *)symbols { + NSString *action = @"/usr/bin/c++filt"; + int count = [symbols count]; + + // It's possible that we have too many symbols on the command line. + // Unfortunately, c++filt doesn't take a file containing names, so we'll + // copy the symbols to a temporary file and use that as stdin. + char buffer[PATH_MAX]; + snprintf(buffer, sizeof(buffer), "/tmp/dump_syms_filtXXXXX"); + int fd = mkstemp(buffer); + char nl = '\n'; + for (unsigned int i = 0; i < count; ++i) { + const char *symbol = [[symbols objectAtIndex:i] UTF8String]; + write(fd, symbol, strlen(symbol)); + write(fd, &nl, 1); + } + + // Reset to the beginning and wrap up with a file handle + lseek(fd, 0, SEEK_SET); + NSArray *args = [NSArray arrayWithObject:@"-n"]; + NSFileHandle *fh = [[NSFileHandle alloc] initWithFileDescriptor:fd + closeOnDealloc:YES]; + NSArray *result = [[self stringFromTask:action args:args standardIn:fh] + componentsSeparatedByString:@"\n"]; + + [fh release]; + + return result; +} + +//============================================================================= +- (void)convertSymbols { + unsigned int count = [cppAddresses_ count]; + NSMutableArray *symbols = [[NSMutableArray alloc] initWithCapacity:count]; + + // Sort addresses for processing + NSArray *addresses = [cppAddresses_ sortedArrayUsingSelector: + @selector(compare:)]; + + for (int i = 0; i < count; ++i) { + NSMutableDictionary *dict = [addresses_ objectForKey: + [addresses objectAtIndex:i]]; + NSString *symbol = [dict objectForKey:kAddressSymbolKey]; + + // Make sure that the symbol is valid + if ([symbol length] < 1) + symbol = kUnknownSymbol; + + [symbols addObject:symbol]; + } + + NSArray *converted = [self convertCPlusPlusSymbols:symbols]; + [symbols release]; + + for (int i = 0; i < count; ++i) { + NSMutableDictionary *dict = [addresses_ objectForKey: + [addresses objectAtIndex:i]]; + NSString *symbol = [converted objectAtIndex:i]; + + // Only add if this is a non-zero length symbol + if ([symbol length]) + [dict setObject:symbol forKey:kAddressConvertedSymbolKey]; + } +} + +//============================================================================= +- (void)addFunction:(NSString *)name line:(int)line address:(uint64_t)address { + NSNumber *addressNum = [NSNumber numberWithUnsignedLongLong:address]; + + if (!address) + return; + + // If the function starts with "_Z" or "__Z" then add it to the list of + // addresses to run through the c++filt + BOOL isCPP = NO; + if ([name length] > 3) { + unichar buffer[3]; + [name getCharacters:buffer range:NSMakeRange(0, 3)]; + if (buffer[0] == '_') { + if (buffer[1] == '_' && buffer[2] == 'Z') { + // Remove the leading underscore + name = [name substringFromIndex:1]; + isCPP = YES; + } else if (buffer[1] == 'Z') { + isCPP = YES; + } + } + } + + if (isCPP) { + // Check if this is some debug symbol that ends in ".eh" I appears as + // though it's got to do with exception handling, but it's not the + // actual address of the function. So we'll ignore them + if ([[name substringFromIndex:[name length] - 3] isEqualToString:@".eh"]) + return; + + if (!cppAddresses_) + cppAddresses_ = [[NSMutableArray alloc] init]; + [cppAddresses_ addObject:addressNum]; + } else if ([name characterAtIndex:0] == '_') { + // Remove the leading underscore + name = [name substringFromIndex:1]; + } + + // If there's already an entry for this address, check and see if we can add + // either the symbol, or a missing line # + NSMutableDictionary *dict = [addresses_ objectForKey:addressNum]; + + if (!dict) { + dict = [[NSMutableDictionary alloc] init]; + [addresses_ setObject:dict forKey:addressNum]; + [dict release]; + } + + if (name && ![dict objectForKey:kAddressSymbolKey]) + [dict setObject:name forKey:kAddressSymbolKey]; + + if (line && ![dict objectForKey:kAddressSourceLineKey]) + [dict setObject:[NSNumber numberWithUnsignedInt:line] + forKey:kAddressSourceLineKey]; + + // Save the function name so that we can add the end of function address + if ([name length]) + lastFunctionStart_ = addressNum; +} + +//============================================================================= +- (BOOL)processSymbolItem:(struct nlist_64 *)list stringTable:(char *)table { + uint32_t n_strx = list->n_un.n_strx; + BOOL result = NO; + + // Extract debugging information: + // Doc: http://developer.apple.com/documentation/DeveloperTools/gdb/stabs/stabs_toc.html + // Header: /usr/include/mach-o/stab.h: + if (list->n_type == N_SO) { + NSString *src = [NSString stringWithUTF8String:&table[n_strx]]; + NSString *ext = [src pathExtension]; + NSNumber *address = [NSNumber numberWithUnsignedLongLong:list->n_value]; + + // TODO(waylonis):Ensure that we get the full path for the source file + // from the first N_SO record + // If there is an extension, we'll consider it source code + if ([ext length]) { + if (!sources_) + sources_ = [[NSMutableDictionary alloc] init]; + // Save the source associated with an address + [sources_ setObject:src forKey:address]; + + result = YES; + } + } else if (list->n_type == N_FUN) { + NSString *fn = [NSString stringWithUTF8String:&table[n_strx]]; + NSRange range = [fn rangeOfString:@":" options:NSBackwardsSearch]; + + if (![fn length] || !range.length) + return NO; + + // The function has a ":" followed by some stuff + fn = [fn substringToIndex:range.location]; + [self addFunction:fn line:list->n_desc address:list->n_value]; + result = YES; + } else if (list->n_type == N_SLINE) { + [self addFunction:nil line:list->n_desc address:list->n_value]; + result = YES; + } else if ((list->n_type & N_TYPE) == N_SECT && + !(list->n_type & N_STAB)) { + // Regular symbols or ones that are external + NSString *fn = [NSString stringWithUTF8String:&table[n_strx]]; + [self addFunction:fn line:0 address:list->n_value]; + result = YES; + } else if (list->n_type == N_ENSYM) { + // End of symbols for current function + if (lastFunctionStart_) { + unsigned long long start = [lastFunctionStart_ unsignedLongLongValue]; + unsigned long long size = list->n_value - start; + NSMutableDictionary *dict = [addresses_ objectForKey:lastFunctionStart_]; + assert(dict); + [dict setObject:[NSNumber numberWithUnsignedLongLong:size] + forKey:kFunctionSizeKey]; + } + } + + return result; +} + +#define SwapLongLongIfNeeded(a) (swap ? NXSwapLongLong(a) : (a)) +#define SwapLongIfNeeded(a) (swap ? NXSwapLong(a) : (a)) +#define SwapIntIfNeeded(a) (swap ? NXSwapInt(a) : (a)) +#define SwapShortIfNeeded(a) (swap ? NXSwapShort(a) : (a)) +//============================================================================= +- (BOOL)loadSymbolInfo:(void *)base offset:(uint32_t)offset { + struct mach_header *header = (struct mach_header *)(base + offset); + BOOL swap = (header->magic == MH_CIGAM); + uint32_t count = SwapLongIfNeeded(header->ncmds); + struct load_command *cmd = (void *)header + sizeof(struct mach_header); + uint32_t symbolTableCommand = SwapLongIfNeeded(LC_SYMTAB); + BOOL result = NO; + + if (!addresses_) + addresses_ = [[NSMutableDictionary alloc] init]; + + for (uint32_t i = 0; cmd && (i < count); i++) { + if (cmd->cmd == symbolTableCommand) { + struct symtab_command *symtab = (struct symtab_command *)cmd; + uint32_t ncmds = SwapLongIfNeeded(symtab->nsyms); + uint32_t symoff = SwapLongIfNeeded(symtab->symoff); + uint32_t stroff = SwapLongIfNeeded(symtab->stroff); + struct nlist *list = (struct nlist *)(base + symoff + offset); + char *strtab = ((char *)header + stroff); + + // Process each command, looking for debugging stuff + for (uint32_t j = 0; j < ncmds; ++j, ++list) { + // Fill in an nlist_64 structure and process with that + struct nlist_64 nlist64 = { + .n_un.n_strx = SwapLongIfNeeded(list->n_un.n_strx), + .n_type = list->n_type, + .n_sect = list->n_sect, + .n_desc = SwapIntIfNeeded(list->n_desc), + .n_value = (uint64_t)SwapLongIfNeeded(list->n_value) + }; + + if ([self processSymbolItem:&nlist64 stringTable:strtab]) + result = YES; + } + } + + uint32_t cmdSize = SwapLongIfNeeded(cmd->cmdsize); + cmd = (struct load_command *)((char *)cmd + cmdSize); + } + + return result; +} + +//============================================================================= +- (BOOL)loadSymbolInfo64:(void *)base offset:(uint32_t)offset { + struct mach_header_64 *header = (struct mach_header_64 *)(base + offset); + BOOL swap = (header->magic == MH_CIGAM_64); + uint32_t count = SwapLongIfNeeded(header->ncmds); + struct load_command *cmd = (void *)header + sizeof(struct mach_header); + uint32_t symbolTableCommand = SwapLongIfNeeded(LC_SYMTAB); + BOOL result = NO; + + for (uint32_t i = 0; cmd && (i < count); i++) { + if (cmd->cmd == symbolTableCommand) { + struct symtab_command *symtab = (struct symtab_command *)cmd; + uint32_t ncmds = SwapLongIfNeeded(symtab->nsyms); + uint32_t symoff = SwapLongIfNeeded(symtab->symoff); + uint32_t stroff = SwapLongIfNeeded(symtab->stroff); + struct nlist_64 *list = (struct nlist_64 *)(base + symoff); + char *strtab = ((char *)header + stroff); + + // Process each command, looking for debugging stuff + for (uint32_t j = 0; j < ncmds; ++j, ++list) { + if (!(list->n_type & (N_STAB | N_TYPE))) + continue; + + // Fill in an nlist_64 structure and process with that + struct nlist_64 nlist64 = { + .n_un.n_strx = SwapLongIfNeeded(list->n_un.n_strx), + .n_type = list->n_type, + .n_sect = list->n_sect, + .n_desc = SwapIntIfNeeded(list->n_desc), + .n_value = SwapLongLongIfNeeded(list->n_value) + }; + + if ([self processSymbolItem:&nlist64 stringTable:strtab]) + result = YES; + } + } + + uint32_t cmdSize = SwapLongIfNeeded(cmd->cmdsize); + cmd = (struct load_command *)((char *)cmd + cmdSize); + } + + return result; +} + +//============================================================================= +- (BOOL)loadSymbolInfoForArchitecture { + NSMutableData *data = [[NSMutableData alloc] + initWithContentsOfMappedFile:sourcePath_]; + NSDictionary *headerInfo = [headers_ objectForKey:architecture_]; + char *base = [data mutableBytes]; + uint32_t offset = + [[headerInfo objectForKey:kHeaderOffsetKey] unsignedLongValue]; + BOOL is64 = [[headerInfo objectForKey:kHeaderIs64BitKey] boolValue]; + BOOL result = is64 ? [self loadSymbolInfo64:base offset:offset] : + [self loadSymbolInfo:base offset:offset]; + + [data release]; + return result; +} + +//============================================================================= +- (BOOL)loadHeader:(void *)base offset:(uint32_t)offset { + struct mach_header *header = (struct mach_header *)(base + offset); + BOOL swap = (header->magic == MH_CIGAM); + uint32_t count = SwapLongIfNeeded(header->ncmds); + struct load_command *cmd = (void *)header + sizeof(struct mach_header); + + for (uint32_t i = 0; cmd && (i < count); i++) { + uint32_t segmentCommand = SwapLongIfNeeded(LC_SEGMENT); + if (cmd->cmd == segmentCommand) { + struct segment_command *seg = (struct segment_command *)cmd; + if (!strcmp(seg->segname, "__TEXT")) { + uint32_t addr = SwapLongIfNeeded(seg->vmaddr); + uint32_t size = SwapLongIfNeeded(seg->vmsize); + cpu_type_t cpu = SwapIntIfNeeded(header->cputype); + NSString *cpuStr = (cpu == CPU_TYPE_I386) ? @"x86" : @"ppc"; + + [headers_ setObject:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithUnsignedLongLong:(uint64_t)addr], + kHeaderBaseAddressKey, + [NSNumber numberWithUnsignedLongLong:(uint64_t)size], kHeaderSizeKey, + [NSNumber numberWithUnsignedLong:offset], kHeaderOffsetKey, + [NSNumber numberWithBool:NO], kHeaderIs64BitKey, + nil] forKey:cpuStr]; + + return YES; + } + } + + uint32_t cmdSize = SwapLongIfNeeded(cmd->cmdsize); + cmd = (struct load_command *)((char *)cmd + cmdSize); + } + + return NO; +} + +//============================================================================= +- (BOOL)loadHeader64:(void *)base offset:(uint32_t)offset { + struct mach_header_64 *header = (struct mach_header_64 *)(base + offset); + BOOL swap = (header->magic == MH_CIGAM_64); + uint32_t count = SwapLongIfNeeded(header->ncmds); + struct load_command *cmd = (void *)header + sizeof(struct mach_header_64); + + for (uint32_t i = 0; cmd && (i < count); i++) { + uint32_t segmentCommand = SwapLongIfNeeded(LC_SEGMENT_64); + if (cmd->cmd == segmentCommand) { + struct segment_command_64 *seg = (struct segment_command_64 *)cmd; + if (!strcmp(seg->segname, "__TEXT")) { + uint64_t addr = SwapLongLongIfNeeded(seg->vmaddr); + uint64_t size = SwapLongLongIfNeeded(seg->vmsize); + cpu_type_t cpu = SwapIntIfNeeded(header->cputype); + cpu &= (~CPU_ARCH_ABI64); + NSString *cpuStr = (cpu == CPU_TYPE_I386) ? @"x86_64" : @"ppc64"; + + [headers_ setObject:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithUnsignedLongLong:addr], kHeaderBaseAddressKey, + [NSNumber numberWithUnsignedLongLong:size], kHeaderSizeKey, + [NSNumber numberWithUnsignedLong:offset], kHeaderOffsetKey, + [NSNumber numberWithBool:YES], kHeaderIs64BitKey, + nil] forKey:cpuStr]; + return YES; + } + } + + uint32_t cmdSize = SwapLongIfNeeded(cmd->cmdsize); + cmd = (struct load_command *)((char *)cmd + cmdSize); + } + + return NO; +} + +//============================================================================= +- (BOOL)loadModuleInfo { + uint64_t result = 0; + NSMutableData *data = [[NSMutableData alloc] + initWithContentsOfMappedFile:sourcePath_]; + char *bytes = [data mutableBytes]; + struct fat_header *fat = (struct fat_header *)bytes; + + if (!fat) { + [data release]; + return 0; + } + + // Gather some information based on the header + BOOL isFat = fat->magic == FAT_MAGIC || fat->magic == FAT_CIGAM; + BOOL is64 = fat->magic == MH_MAGIC_64 || fat->magic == MH_CIGAM_64; + BOOL is32 = fat->magic == MH_MAGIC || fat->magic == MH_CIGAM; + BOOL swap = fat->magic == FAT_CIGAM || fat->magic == MH_CIGAM_64 || + fat->magic == MH_CIGAM; + + if (!is64 && !is32 && !isFat) { + [data release]; + return 0; + } + + // Load any available architectures and save the information + headers_ = [[NSMutableDictionary alloc] init]; + + if (isFat) { + struct fat_arch *archs = + (struct fat_arch *)((void *)fat + sizeof(struct fat_header)); + uint32_t count = SwapLongIfNeeded(fat->nfat_arch); + + for (uint32_t i = 0; i < count; ++i) { + archs[i].cputype = SwapIntIfNeeded(archs[i].cputype); + archs[i].cpusubtype = SwapIntIfNeeded(archs[i].cpusubtype); + archs[i].offset = SwapLongIfNeeded(archs[i].offset); + archs[i].size = SwapLongIfNeeded(archs[i].size); + archs[i].align = SwapLongIfNeeded(archs[i].align); + + if (archs[i].cputype & CPU_ARCH_ABI64) + result = [self loadHeader64:bytes offset:archs[i].offset]; + else + result = [self loadHeader:bytes offset:archs[i].offset]; + } + } else if (is32) { + result = [self loadHeader:bytes offset:0]; + } else { + result = [self loadHeader64:bytes offset:0]; + } + + [data release]; + return result; +} + +//============================================================================= +static BOOL WriteFormat(int fd, const char *fmt, ...) { + va_list list; + char buffer[4096]; + ssize_t expected, written; + + va_start(list, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, list); + expected = strlen(buffer); + written = write(fd, buffer, strlen(buffer)); + va_end(list); + + return expected == written; +} + +//============================================================================= +- (BOOL)outputSymbolFile:(int)fd { + // Get the baseAddress for this architecture + NSDictionary *archDict = [headers_ objectForKey:architecture_]; + NSNumber *baseAddressNum = [archDict objectForKey:kHeaderBaseAddressKey]; + uint64_t baseAddress = + baseAddressNum ? [baseAddressNum unsignedLongLongValue] : 0; + NSNumber *moduleSizeNum = [archDict objectForKey:kHeaderSizeKey]; + uint64_t moduleSize = + moduleSizeNum ? [moduleSizeNum unsignedLongLongValue] : 0; + + lastFunctionStart_ = nil; + + // Gather the information + [self loadSymbolInfoForArchitecture]; + [self convertSymbols]; + + NSArray *sortedAddresses = [[addresses_ allKeys] + sortedArrayUsingSelector:@selector(compare:)]; + + // Module + if (!WriteFormat(fd, "MODULE mac %s %s 1 %s\n", [architecture_ UTF8String], + [uuidStr_ UTF8String], + [[sourcePath_ lastPathComponent] UTF8String])) + return NO; + + // Sources ordered by address + NSArray *sources = [[sources_ allKeys] + sortedArrayUsingSelector:@selector(compare:)]; + int sourceCount = [sources count]; + for (int i = 0; i < sourceCount; ++i) { + NSString *file = [sources_ objectForKey:[sources objectAtIndex:i]]; + if (!WriteFormat(fd, "FILE %d %s\n", i + 1, [file UTF8String])) + return NO; + } + + // Symbols + char terminatingChar = '\n'; + uint32_t fileIdx = 0, nextFileIdx = 0; + uint64_t nextSourceFileAddress = 0; + NSNumber *nextAddress; + uint64_t nextAddressVal; + int addressCount = [sortedAddresses count]; + + for (int i = 0; i < addressCount; ++i) { + NSNumber *address = [sortedAddresses objectAtIndex:i]; + uint64_t addressVal = [address unsignedLongLongValue] - baseAddress; + + // Get the next address to calculate the length + if (i + 1 < addressCount) { + nextAddress = [sortedAddresses objectAtIndex:i + 1]; + nextAddressVal = [nextAddress unsignedLongLongValue] - baseAddress; + } else { + nextAddressVal = baseAddress + moduleSize; + // The symbol reader doesn't want a trailing newline + terminatingChar = '\0'; + } + + NSDictionary *dict = [addresses_ objectForKey:address]; + NSNumber *line = [dict objectForKey:kAddressSourceLineKey]; + NSString *symbol = [dict objectForKey:kAddressConvertedSymbolKey]; + + // Find the source file (if any) that contains this address + while (sourceCount && (addressVal >= nextSourceFileAddress)) { + fileIdx = nextFileIdx; + + if (nextFileIdx < sourceCount) { + NSNumber *addr = [sources objectAtIndex:nextFileIdx]; + ++nextFileIdx; + nextSourceFileAddress = [addr unsignedLongLongValue] - baseAddress; + } else { + nextSourceFileAddress = baseAddress + moduleSize; + } + } + + if (!symbol) + symbol = [dict objectForKey:kAddressSymbolKey]; + + if (line) { + if (symbol) { + NSNumber *functionLength = [dict objectForKey:kFunctionSizeKey]; + uint64_t functionLengthVal = [functionLength unsignedLongLongValue]; + + // Function + if (!WriteFormat(fd, "FUNC %llx %llx 0 %s%c", addressVal, + functionLengthVal, [symbol UTF8String], + terminatingChar)) + return NO; + } + + // Source line + uint64_t length = nextAddressVal - addressVal; + if (!WriteFormat(fd, "%llx %llx %d %d%c", addressVal, length, + [line unsignedIntValue], fileIdx, terminatingChar)) + return NO; + } else { + // PUBLIC
+ if (!WriteFormat(fd, "PUBLIC %llx 0 %s%c", addressVal, + [symbol UTF8String], terminatingChar)) + return NO; + } + } + + return YES; +} + +//============================================================================= +- (id)initWithContentsOfFile:(NSString *)path { + if (self = [super init]) { + + if (![[NSFileManager defaultManager] fileExistsAtPath:path]) { + NSLog(@"Missing source file: %@", path); + [self autorelease]; + return nil; + } + + sourcePath_ = [path copy]; + + if (![self loadModuleInfo]) { + NSLog(@"Not a valid Mach-o file: %@", path); + [self autorelease]; + return nil; + } + + // If there's more than one, use the native one + if ([headers_ count] > 1) { + const NXArchInfo *localArchInfo = NXGetLocalArchInfo(); + + if (localArchInfo) { + cpu_type_t cpu = localArchInfo->cputype; + NSString *arch; + + if (cpu & CPU_ARCH_ABI64) + arch = ((cpu & ~CPU_ARCH_ABI64) == CPU_TYPE_X86) ? + @"x86_64" : @"ppc64"; + else + arch = (cpu == CPU_TYPE_X86) ? @"x86" : @"ppc"; + + [self setArchitecture:arch]; + } + } else { + // Specify the default architecture + [self setArchitecture:[[headers_ allKeys] objectAtIndex:0]]; + } + + [self setUUID:nil]; + } + + return self; +} + +//============================================================================= +- (NSArray *)availableArchitectures { + return [headers_ allKeys]; +} + +//============================================================================= +- (void)dealloc { + [sourcePath_ release]; + [architecture_ release]; + [uuidStr_ release]; + [addresses_ release]; + [sources_ release]; + [headers_ release]; + + [super dealloc]; +} + +//============================================================================= +- (BOOL)setArchitecture:(NSString *)architecture { + NSString *normalized = [architecture lowercaseString]; + BOOL isValid = NO; + + if ([normalized isEqualToString:@"ppc"]) { + isValid = YES; + } + else if ([normalized isEqualToString:@"i386"]) { + normalized = @"x86"; + isValid = YES; + } + else if ([normalized isEqualToString:@"x86"]) { + isValid = YES; + } + else if ([normalized isEqualToString:@"ppc64"]) { + isValid = YES; + } + else if ([normalized isEqualToString:@"x86_64"]) { + isValid = YES; + } + + if (isValid) { + if (![headers_ objectForKey:normalized]) + return NO; + + [architecture_ autorelease]; + architecture_ = [architecture copy]; + } + + return isValid; +} + +//============================================================================= +- (NSString *)architecture { + return architecture_; +} + +//============================================================================= +- (void)setUUID:(NSString *)uuidStr { + if (!uuidStr) { + CFUUIDRef uuid = CFUUIDCreate(NULL); + uuidStr = [(NSString *)CFUUIDCreateString(NULL, uuid) autorelease]; + } + + [uuidStr_ autorelease]; + uuidStr_ = [uuidStr copy]; +} + +//============================================================================= +- (NSString *)uuid { + return uuidStr_; +} + +//============================================================================= +- (BOOL)writeSymbolFile:(NSString *)destinationPath { + const char *dest = [destinationPath fileSystemRepresentation]; + int fd; + + if ([[destinationPath substringToIndex:1] isEqualToString:@"-"]) + fd = STDOUT_FILENO; + else + fd = open(dest, O_WRONLY | O_CREAT | O_TRUNC, 0666); + + if (fd == -1) + return NO; + + BOOL result = [self outputSymbolFile:fd]; + + close(fd); + + return result; +} + +@end diff --git a/src/tools/mac/dump_syms/dump_syms.xcodeproj/project.pbxproj b/src/tools/mac/dump_syms/dump_syms.xcodeproj/project.pbxproj new file mode 100644 index 00000000..b51a3f19 --- /dev/null +++ b/src/tools/mac/dump_syms/dump_syms.xcodeproj/project.pbxproj @@ -0,0 +1,215 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 42; + objects = { + +/* Begin PBXBuildFile section */ + 8DD76F9A0486AA7600D96B5E /* dump_syms.m in Sources */ = {isa = PBXBuildFile; fileRef = 08FB7796FE84155DC02AAC07 /* dump_syms.m */; settings = {ATTRIBUTES = (); }; }; + 8DD76F9C0486AA7600D96B5E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08FB779EFE84155DC02AAC07 /* Foundation.framework */; }; + 9BDF186F0B1BB43700F8391B /* dump_syms.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9BDF186D0B1BB43700F8391B /* dump_syms.h */; }; + 9BDF18700B1BB43700F8391B /* dump_syms_tool.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BDF186E0B1BB43700F8391B /* dump_syms_tool.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 8DD76F9E0486AA7600D96B5E /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 8; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + 9BDF186F0B1BB43700F8391B /* dump_syms.h in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 08FB7796FE84155DC02AAC07 /* dump_syms.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = dump_syms.m; path = ../../../common/mac/dump_syms.m; sourceTree = ""; }; + 08FB779EFE84155DC02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; + 8DD76FA10486AA7600D96B5E /* dump_syms */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = dump_syms; sourceTree = BUILT_PRODUCTS_DIR; }; + 9BDF186D0B1BB43700F8391B /* dump_syms.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = dump_syms.h; path = ../../../common/mac/dump_syms.h; sourceTree = ""; }; + 9BDF186E0B1BB43700F8391B /* dump_syms_tool.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = dump_syms_tool.m; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 8DD76F9B0486AA7600D96B5E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8DD76F9C0486AA7600D96B5E /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 08FB7794FE84155DC02AAC07 /* dump_syms */ = { + isa = PBXGroup; + children = ( + 9BDF186D0B1BB43700F8391B /* dump_syms.h */, + 08FB7796FE84155DC02AAC07 /* dump_syms.m */, + 9BDF186E0B1BB43700F8391B /* dump_syms_tool.m */, + 08FB779DFE84155DC02AAC07 /* External Frameworks and Libraries */, + 1AB674ADFE9D54B511CA2CBB /* Products */, + ); + name = dump_syms; + sourceTree = ""; + }; + 08FB779DFE84155DC02AAC07 /* External Frameworks and Libraries */ = { + isa = PBXGroup; + children = ( + 08FB779EFE84155DC02AAC07 /* Foundation.framework */, + ); + name = "External Frameworks and Libraries"; + sourceTree = ""; + }; + 1AB674ADFE9D54B511CA2CBB /* Products */ = { + isa = PBXGroup; + children = ( + 8DD76FA10486AA7600D96B5E /* dump_syms */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 8DD76F960486AA7600D96B5E /* dump_syms */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1DEB927408733DD40010E9CD /* Build configuration list for PBXNativeTarget "dump_syms" */; + buildPhases = ( + 8DD76F990486AA7600D96B5E /* Sources */, + 8DD76F9B0486AA7600D96B5E /* Frameworks */, + 8DD76F9E0486AA7600D96B5E /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = dump_syms; + productInstallPath = "$(HOME)/bin"; + productName = dump_syms; + productReference = 8DD76FA10486AA7600D96B5E /* dump_syms */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 08FB7793FE84155DC02AAC07 /* Project object */ = { + isa = PBXProject; + buildConfigurationList = 1DEB927808733DD40010E9CD /* Build configuration list for PBXProject "dump_syms" */; + hasScannedForEncodings = 1; + mainGroup = 08FB7794FE84155DC02AAC07 /* dump_syms */; + projectDirPath = ""; + targets = ( + 8DD76F960486AA7600D96B5E /* dump_syms */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 8DD76F990486AA7600D96B5E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8DD76F9A0486AA7600D96B5E /* dump_syms.m in Sources */, + 9BDF18700B1BB43700F8391B /* dump_syms_tool.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 1DEB927508733DD40010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = NO; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = YES; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_MODEL_TUNING = G5; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = ""; + HEADER_SEARCH_PATHS = ( + "../../../**", + "$(inherited)", + ); + INSTALL_PATH = "$(HOME)/bin"; + PRODUCT_NAME = dump_syms; + ZERO_LINK = NO; + }; + name = Debug; + }; + 1DEB927608733DD40010E9CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = ( + ppc, + i386, + ); + GCC_C_LANGUAGE_STANDARD = c99; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_GENERATE_DEBUGGING_SYMBOLS = NO; + GCC_MODEL_TUNING = G5; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = ""; + HEADER_SEARCH_PATHS = ( + "../../../**", + "$(inherited)", + ); + INSTALL_PATH = "$(HOME)/bin"; + PRODUCT_NAME = dump_syms; + ZERO_LINK = NO; + }; + name = Release; + }; + 1DEB927908733DD40010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + PREBINDING = NO; + SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk; + }; + name = Debug; + }; + 1DEB927A08733DD40010E9CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + PREBINDING = NO; + SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1DEB927408733DD40010E9CD /* Build configuration list for PBXNativeTarget "dump_syms" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB927508733DD40010E9CD /* Debug */, + 1DEB927608733DD40010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1DEB927808733DD40010E9CD /* Build configuration list for PBXProject "dump_syms" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB927908733DD40010E9CD /* Debug */, + 1DEB927A08733DD40010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; +} diff --git a/src/tools/mac/dump_syms/dump_syms_tool.m b/src/tools/mac/dump_syms/dump_syms_tool.m new file mode 100644 index 00000000..60499a88 --- /dev/null +++ b/src/tools/mac/dump_syms/dump_syms_tool.m @@ -0,0 +1,139 @@ +// Copyright (c) 2006, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// dump_syms_tool.m: Command line tool that uses the DumpSymbols class. +// TODO(waylonis): accept stdin + +#include +#include + +#include "dump_syms.h" + +typedef struct { + NSString *srcPath; + NSString *arch; + NSString *uuidStr; + BOOL result; +} Options; + +//============================================================================= +static void Start(Options *options) { + DumpSymbols *dump = [[DumpSymbols alloc] + initWithContentsOfFile:options->srcPath]; + + [dump setArchitecture:options->arch]; + [dump setUUID:options->uuidStr]; + options->result = [dump writeSymbolFile:@"-"]; +} + +//============================================================================= +static void Usage(int argc, const char *argv[]) { + fprintf(stderr, "Output airbag symbol file. If a UUID is not specified,\n" + "the program will try to locate one in the mach-o-file. If one is\n" + "not found, one will be created.\n"); + fprintf(stderr, "Usage: %s [-a ppc|i386|x86][-u uuid] \n", + argv[0]); + fprintf(stderr, "\t-a: Architecture type [default: native]\n"); + fprintf(stderr, "\t-u: Specify a UUID\n"); + fprintf(stderr, "\t-h: Usage\n"); + fprintf(stderr, "\t-?: Usage\n"); +} + +//============================================================================= +static void SetupOptions(int argc, const char *argv[], Options *options) { + extern int optind; + const NXArchInfo *localArchInfo = NXGetLocalArchInfo(); + char ch; + + if (localArchInfo) { + if (localArchInfo->cputype & CPU_ARCH_ABI64) + options->arch = (localArchInfo->cputype == CPU_TYPE_POWERPC64) ? @"ppc64": + @"x86_64"; + else + options->arch = (localArchInfo->cputype == CPU_TYPE_POWERPC) ? @"ppc" : + @"x86"; + } + + while ((ch = getopt(argc, (char * const *)argv, "a:h?")) != -1) { + switch (ch) { + case 'a': + if (strcmp("ppc", optarg) == 0) + options->arch = @"ppc"; + else if (strcmp("x86", optarg) == 0 || strcmp("i386", optarg) == 0) + options->arch = @"x86"; + else if (strcmp("ppc64", optarg) == 0) + options->arch = @"ppc64"; + else if (strcmp("x86_64", optarg) == 0) + options->arch = @"x86_64"; + else { + fprintf(stderr, "%s: Invalid architecture: %s\n", argv[0], optarg); + Usage(argc, argv); + exit(1); + } + break; + case 'u': + options->uuidStr = [NSString stringWithUTF8String:optarg]; + break; + case '?': + case 'h': + Usage(argc, argv); + exit(0); + break; + } + } + + if (!options->uuidStr) { + CFUUIDRef uuid = CFUUIDCreate(NULL); + options->uuidStr = (NSString *)CFUUIDCreateString(NULL, uuid); + } + + if ((argc - optind) != 1) { + fprintf(stderr, "%s: Missing executable\n", argv[0]); + Usage(argc, argv); + exit(1); + } + + options->srcPath = [[NSFileManager defaultManager] + stringWithFileSystemRepresentation:argv[optind] + length:strlen(argv[optind])]; +} + +//============================================================================= +int main (int argc, const char * argv[]) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + Options options; + + bzero(&options, sizeof(Options)); + SetupOptions(argc, argv, &options); + Start(&options); + + [pool release]; + + return options.result; +}