Source/PLCrashReport.m (542 lines of code) (raw):

/* * Author: Landon Fuller <landonf@plausiblelabs.com> * * Copyright (c) 2008-2013 Plausible Labs Cooperative, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #if __has_include(<CrashReporter/PLCrashReport.h>) #import <CrashReporter/CrashReporter.h> #import <CrashReporter/PLCrashReport.h> #else #import "CrashReporter.h" #import "PLCrashReport.h" #endif #import "PLCrashReport.pb-c.h" #import "PLCrashAsyncThread.h" struct _PLCrashReportDecoder { Plcrash__CrashReport *crashReport; }; @interface PLCrashReport (PrivateMethods) - (Plcrash__CrashReport *) decodeCrashData: (NSData *) data error: (NSError **) outError; - (PLCrashReportSystemInfo *) extractSystemInfo: (Plcrash__CrashReport__SystemInfo *) systemInfo processorInfo: (PLCrashReportProcessorInfo *) processorInfo error: (NSError **) outError; - (PLCrashReportProcessorInfo *) synthesizeProcessorInfoFromArchitecture: (Plcrash__Architecture) architecture error: (NSError **) outError; - (PLCrashReportProcessorInfo *) extractProcessorInfo: (Plcrash__CrashReport__Processor *) processorInfo error: (NSError **) outError; - (PLCrashReportMachineInfo *) extractMachineInfo: (Plcrash__CrashReport__MachineInfo *) machineInfo error: (NSError **) outError; - (PLCrashReportApplicationInfo *) extractApplicationInfo: (Plcrash__CrashReport__ApplicationInfo *) applicationInfo error: (NSError **) outError; - (PLCrashReportProcessInfo *) extractProcessInfo: (Plcrash__CrashReport__ProcessInfo *) processInfo error: (NSError **) outError; - (NSArray *) extractThreadInfo: (Plcrash__CrashReport *) crashReport error: (NSError **) outError; - (NSArray *) extractImageInfo: (Plcrash__CrashReport *) crashReport error: (NSError **) outError; - (PLCrashReportExceptionInfo *) extractExceptionInfo: (Plcrash__CrashReport__Exception *) exceptionInfo error: (NSError **) outError; - (PLCrashReportSignalInfo *) extractSignalInfo: (Plcrash__CrashReport__Signal *) signalInfo error: (NSError **) outError; - (PLCrashReportMachExceptionInfo *) extractMachExceptionInfo: (Plcrash__CrashReport__Signal__MachException *) machExceptionInfo error: (NSError **) outError; @end static void populate_nserror (NSError **error, PLCrashReporterError code, NSString *description); /** * Provides decoding of crash logs generated by the PLCrashReporter framework. * * @warning This API should be considered in-development and subject to change. */ @implementation PLCrashReport /** * Initialize with the provided crash log data. On error, nil will be returned, and * an NSError instance will be provided via @a error, if non-NULL. * * @param encodedData Encoded plcrash crash log. * @param outError If an error occurs, this pointer will contain an NSError object * indicating why the crash log could not be parsed. If no error occurs, this parameter * will be left unmodified. You may specify NULL for this parameter, and no error information * will be provided. * * @par Designated Initializer * This method is the designated initializer for the PLCrashReport class. */ - (id) initWithData: (NSData *) encodedData error: (NSError **) outError { if ((self = [super init]) == nil) { // This shouldn't happen, but we have to fufill our API contract populate_nserror(outError, PLCrashReporterErrorUnknown, @"Could not initialize superclass"); return nil; } /* Allocate the struct and attempt to parse */ _decoder = malloc(sizeof(_PLCrashReportDecoder)); _decoder->crashReport = [self decodeCrashData: encodedData error: outError]; /* Check if decoding failed. If so, outError has already been populated. */ if (_decoder->crashReport == NULL) { goto error; } /* Report info (optional) */ _uuid = NULL; if (_decoder->crashReport->report_info != NULL) { /* Report UUID (optional) * If our minimum supported target is bumped to (10.8+, iOS 6.0+), NSUUID should * be used instead. */ if (_decoder->crashReport->report_info->has_uuid) { /* Validate the UUID length */ if (_decoder->crashReport->report_info->uuid.len != sizeof(uuid_t)) { populate_nserror(outError, PLCrashReporterErrorCrashReportInvalid , @"Report UUID value is not a standard 16 bytes"); goto error; } CFUUIDBytes uuid_bytes; memcpy(&uuid_bytes, _decoder->crashReport->report_info->uuid.data, _decoder->crashReport->report_info->uuid.len); _uuid = CFUUIDCreateFromUUIDBytes(NULL, uuid_bytes); } } /* Machine info */ if (_decoder->crashReport->machine_info != NULL) { _machineInfo = [self extractMachineInfo: _decoder->crashReport->machine_info error: outError]; if (!_machineInfo) goto error; } /* System info */ _systemInfo = [self extractSystemInfo: _decoder->crashReport->system_info processorInfo: _machineInfo.processorInfo error: outError]; if (!_systemInfo) goto error; /* Application info */ _applicationInfo = [self extractApplicationInfo: _decoder->crashReport->application_info error: outError]; if (!_applicationInfo) goto error; /* Process info. Handle missing info gracefully -- it is only included in v1.1+ crash reports. */ if (_decoder->crashReport->process_info != NULL) { _processInfo = [self extractProcessInfo: _decoder->crashReport->process_info error:outError]; if (!_processInfo) goto error; } /* Signal info */ _signalInfo = [self extractSignalInfo: _decoder->crashReport->signal error: outError]; if (!_signalInfo) goto error; /* Mach exception info */ if (_decoder->crashReport->signal != NULL && _decoder->crashReport->signal->mach_exception != NULL) { _machExceptionInfo = [self extractMachExceptionInfo: _decoder->crashReport->signal->mach_exception error: outError]; if (!_machExceptionInfo) goto error; } /* Thread info */ _threads = [self extractThreadInfo: _decoder->crashReport error: outError]; if (!_threads) goto error; /* Image info */ _images = [self extractImageInfo: _decoder->crashReport error: outError]; if (!_images) goto error; /* Exception info, if it is available */ if (_decoder->crashReport->exception != NULL) { _exceptionInfo = [self extractExceptionInfo: _decoder->crashReport->exception error: outError]; if (!_exceptionInfo) goto error; } /* Custom data, if it is available */ if (_decoder->crashReport->has_custom_data) { _customData = [NSData dataWithBytes:_decoder->crashReport->custom_data.data length:_decoder->crashReport->custom_data.len]; if (!_customData) goto error; } return self; error: return nil; } - (void) dealloc { if (_uuid != NULL) CFRelease(_uuid); /* Free the decoder state */ if (_decoder != NULL) { if (_decoder->crashReport != NULL) { protobuf_c_message_free_unpacked((ProtobufCMessage *) _decoder->crashReport, NULL); } free(_decoder); _decoder = NULL; } } /** * Return the binary image containing the given address, or nil if no binary image * is found. * * @param address The address to search for. */ - (PLCrashReportBinaryImageInfo *) imageForAddress: (uint64_t) address { for (PLCrashReportBinaryImageInfo *imageInfo in self.images) { uint64_t normalizedBaseAddress = imageInfo.imageBaseAddress; if (normalizedBaseAddress <= address && address < (normalizedBaseAddress + imageInfo.imageSize)) return imageInfo; } /* Not found */ return nil; } // property getter. Returns YES if machine information is available. - (BOOL) hasMachineInfo { if (_machineInfo != nil) return YES; return NO; } // property getter. Returns YES if process information is available. - (BOOL) hasProcessInfo { if (_processInfo != nil) return YES; return NO; } // property getter. Returns YES if exception information is available. - (BOOL) hasExceptionInfo { if (_exceptionInfo != nil) return YES; return NO; } @synthesize systemInfo = _systemInfo; @synthesize machineInfo = _machineInfo; @synthesize applicationInfo = _applicationInfo; @synthesize processInfo = _processInfo; @synthesize signalInfo = _signalInfo; @synthesize machExceptionInfo = _machExceptionInfo; @synthesize threads = _threads; @synthesize images = _images; @synthesize exceptionInfo = _exceptionInfo; @synthesize uuidRef = _uuid; @end /** * @internal * Private Methods */ @implementation PLCrashReport (PrivateMethods) /** * Decode the crash log message. * * @warning MEMORY WARNING. The caller is responsible for deallocating th ePlcrash__CrashReport instance * returned by this method via protobuf_c_message_free_unpacked(). */ - (Plcrash__CrashReport *) decodeCrashData: (NSData *) data error: (NSError **) outError { const struct PLCrashReportFileHeader *header; const void *bytes; bytes = [data bytes]; header = bytes; /* Verify that the crash log is sufficently large */ if (sizeof(struct PLCrashReportFileHeader) >= [data length]) { populate_nserror(outError, PLCrashReporterErrorCrashReportInvalid, NSLocalizedString(@"Could not decode truncated crash log", @"Crash log decoding error message")); return NULL; } /* Check the file magic */ if (memcmp(header->magic, PLCRASH_REPORT_FILE_MAGIC, strlen(PLCRASH_REPORT_FILE_MAGIC)) != 0) { populate_nserror(outError, PLCrashReporterErrorCrashReportInvalid,NSLocalizedString(@"Could not decode invalid crash log header", @"Crash log decoding error message")); return NULL; } /* Check the version */ if(header->version != PLCRASH_REPORT_FILE_VERSION) { populate_nserror(outError, PLCrashReporterErrorCrashReportInvalid, [NSString stringWithFormat: NSLocalizedString(@"Could not decode unsupported crash report version: %d", @"Crash log decoding message"), header->version]); return NULL; } Plcrash__CrashReport *crashReport = plcrash__crash_report__unpack(NULL, [data length] - sizeof(struct PLCrashReportFileHeader), header->data); if (crashReport == NULL) { populate_nserror(outError, PLCrashReporterErrorCrashReportInvalid, NSLocalizedString(@"An unknown error occured decoding the crash report", @"Crash log decoding error message")); return NULL; } return crashReport; } /** * Extract system information from the crash log. Returns nil on error. * * @param systemInfo The system info from the protobuf file. * @param processorInfo The system info from the machine info. This may be nil for v1 reports, in which case the * information will be synthesized from the architecture in the @a systemInfo. * @param outError A pointer to an NSError object variable. If an error occurs, this pointer will contain an error * object indicating why the system info could not be extracted. If no error occurs, this parameter will be left * unmodified. You may specify nil for this parameter, and no error information will be provided. * * @return Returns the system information, or nil on failure. */ - (PLCrashReportSystemInfo *) extractSystemInfo: (Plcrash__CrashReport__SystemInfo *) systemInfo processorInfo: (PLCrashReportProcessorInfo *) processorInfo error: (NSError **) outError { NSDate *timestamp = nil; NSString *osBuild = nil; /* Validate */ if (systemInfo == NULL) { populate_nserror(outError, PLCrashReporterErrorCrashReportInvalid, NSLocalizedString(@"Crash report is missing System Information section", @"Missing sysinfo in crash report")); return nil; } if (systemInfo->os_version == NULL) { populate_nserror(outError, PLCrashReporterErrorCrashReportInvalid, NSLocalizedString(@"Crash report is missing System Information OS version field", @"Missing sysinfo operating system in crash report")); return nil; } /* Set up the build, if available */ if (systemInfo->os_build != NULL) osBuild = [NSString stringWithUTF8String: systemInfo->os_build]; /* Set up the timestamp, if available */ if (systemInfo->timestamp != 0) timestamp = [NSDate dateWithTimeIntervalSince1970: systemInfo->timestamp]; /* v1 crash logs will not have machine info, so the only data available to * us is the deprecated architecture field. From that we will generate a * PLCrashReportProcessorInfo object so that library users don't have to * get at the architecture information multiple ways. */ if (processorInfo == nil) { processorInfo = [self synthesizeProcessorInfoFromArchitecture: systemInfo->architecture error: outError]; if (processorInfo == nil) return nil; } /* Done */ return [[PLCrashReportSystemInfo alloc] initWithOperatingSystem: (PLCrashReportOperatingSystem) systemInfo->operating_system operatingSystemVersion: [NSString stringWithUTF8String: systemInfo->os_version] operatingSystemBuild: osBuild architecture: (PLCrashReportArchitecture) systemInfo->architecture processorInfo: processorInfo timestamp: timestamp]; } /** * Extract processor information from the crash log. Returns nil on error. */ - (PLCrashReportProcessorInfo *) extractProcessorInfo: (Plcrash__CrashReport__Processor *) processorInfo error: (NSError **) outError { /* Validate */ if (processorInfo == NULL) { populate_nserror(outError, PLCrashReporterErrorCrashReportInvalid, NSLocalizedString(@"Crash report is missing processor info section", @"Missing processor info in crash report")); return nil; } return [[PLCrashReportProcessorInfo alloc] initWithTypeEncoding: (PLCrashReportProcessorTypeEncoding) processorInfo->encoding type: processorInfo->type subtype: processorInfo->subtype]; } /** * Synthesize a processor information object from an architecture type. Returns nil on error. */ - (PLCrashReportProcessorInfo *) synthesizeProcessorInfoFromArchitecture: (Plcrash__Architecture) architecture error:(NSError **)outError { uint64_t processorType; uint64_t processorSubtype; switch (architecture) { case PLCRASH__ARCHITECTURE__X86_32: processorType = CPU_TYPE_X86; processorSubtype = CPU_SUBTYPE_X86_ALL; break; case PLCRASH__ARCHITECTURE__X86_64: processorType = CPU_TYPE_X86_64; processorSubtype = CPU_SUBTYPE_X86_64_ALL; break; case PLCRASH__ARCHITECTURE__PPC: processorType = CPU_TYPE_POWERPC; processorSubtype = CPU_SUBTYPE_POWERPC_ALL; break; case PLCRASH__ARCHITECTURE__PPC64: processorType = CPU_TYPE_POWERPC64; processorSubtype = CPU_SUBTYPE_POWERPC_ALL; break; case PLCRASH__ARCHITECTURE__ARMV6: processorType = CPU_TYPE_ARM; processorSubtype = CPU_SUBTYPE_ARM_V6; break; case PLCRASH__ARCHITECTURE__ARMV7: processorType = CPU_TYPE_ARM; processorSubtype = CPU_SUBTYPE_ARM_V7; break; default: populate_nserror(outError, PLCrashReporterErrorCrashReportInvalid, NSLocalizedString(@"Crash report has an unknown architecture", @"Unknown architecture in crash report")); return nil; } return [[PLCrashReportProcessorInfo alloc] initWithTypeEncoding: PLCrashReportProcessorTypeEncodingMach type: processorType subtype: processorSubtype]; } /** * Extract machine information from the crash log. Returns nil on error. */ - (PLCrashReportMachineInfo *) extractMachineInfo: (Plcrash__CrashReport__MachineInfo *) machineInfo error: (NSError **) outError { NSString *model = nil; PLCrashReportProcessorInfo *processorInfo = nil; /* Validate */ if (machineInfo == NULL) { populate_nserror(outError, PLCrashReporterErrorCrashReportInvalid, NSLocalizedString(@"Crash report is missing Machine Information section", @"Missing machine_info in crash report")); return nil; } /* Set up the model, if available */ if (machineInfo->model != NULL) model = [NSString stringWithUTF8String: machineInfo->model]; /* Set up the processor info. */ if (machineInfo->processor != NULL) { processorInfo = [self extractProcessorInfo: machineInfo->processor error: outError]; if (processorInfo == nil) return nil; } /* Done */ return [[PLCrashReportMachineInfo alloc] initWithModelName: model processorInfo: processorInfo processorCount: machineInfo->processor_count logicalProcessorCount: machineInfo->logical_processor_count]; } /** * Extract application information from the crash log. Returns nil on error. */ - (PLCrashReportApplicationInfo *) extractApplicationInfo: (Plcrash__CrashReport__ApplicationInfo *) applicationInfo error: (NSError **) outError { NSString *marketingVersion = nil; /* Validate */ if (applicationInfo == NULL) { populate_nserror(outError, PLCrashReporterErrorCrashReportInvalid, NSLocalizedString(@"Crash report is missing Application Information section", @"Missing app info in crash report")); return nil; } /* Identifier available? */ if (applicationInfo->identifier == NULL) { populate_nserror(outError, PLCrashReporterErrorCrashReportInvalid, NSLocalizedString(@"Crash report is missing Application Information app identifier field", @"Missing app identifier in crash report")); return nil; } /* Version available? */ if (applicationInfo->version == NULL) { populate_nserror(outError, PLCrashReporterErrorCrashReportInvalid, NSLocalizedString(@"Crash report is missing Application Information app version field", @"Missing app version in crash report")); return nil; } /* Marketing Version available? */ if (applicationInfo->marketing_version != NULL) { marketingVersion = [NSString stringWithUTF8String: applicationInfo->marketing_version]; } /* Done */ NSString *identifier = [NSString stringWithUTF8String: applicationInfo->identifier]; NSString *version = [NSString stringWithUTF8String: applicationInfo->version]; return [[PLCrashReportApplicationInfo alloc] initWithApplicationIdentifier: identifier applicationVersion: version applicationMarketingVersion:marketingVersion]; } /** * Extract process information from the crash log. Returns nil on error. */ - (PLCrashReportProcessInfo *) extractProcessInfo: (Plcrash__CrashReport__ProcessInfo *) processInfo error: (NSError **) outError { /* Validate */ if (processInfo == NULL) { populate_nserror(outError, PLCrashReporterErrorCrashReportInvalid, NSLocalizedString(@"Crash report is missing Process Information section", @"Missing process info in crash report")); return nil; } /* Name available? */ NSString *processName = nil; if (processInfo->process_name != NULL) processName = [NSString stringWithUTF8String: processInfo->process_name]; /* Path available? */ NSString *processPath = nil; if (processInfo->process_path != NULL) processPath = [NSString stringWithUTF8String: processInfo->process_path]; /* Start time available? */ NSDate *startTime = nil; if (processInfo->has_start_time) startTime = [NSDate dateWithTimeIntervalSince1970: processInfo->start_time]; /* Parent Name available? */ NSString *parentProcessName = nil; if (processInfo->parent_process_name != NULL) parentProcessName = [NSString stringWithUTF8String: processInfo->parent_process_name]; /* Required elements */ NSUInteger processID = processInfo->process_id; NSUInteger parentProcessID = processInfo->parent_process_id; /* Done */ return [[PLCrashReportProcessInfo alloc] initWithProcessName: processName processID: processID processPath: processPath processStartTime: startTime parentProcessName: parentProcessName parentProcessID: parentProcessID native: processInfo->native]; } /** * Extract symbol information from the crash log. Returns nil on error, or a PLCrashReportSymbolInfo * instance on success. */ - (PLCrashReportSymbolInfo *) extractSymbolInfo: (Plcrash__CrashReport__Symbol *) symbol error: (NSError **) outError { if (symbol == NULL) { populate_nserror(outError, PLCrashReporterErrorCrashReportInvalid, NSLocalizedString(@"Crash report is missing symbol information", @"Missing symbol info in crash report")); return nil; } NSString *name = [NSString stringWithUTF8String: symbol->name]; return [[PLCrashReportSymbolInfo alloc] initWithSymbolName: name startAddress: symbol->start_address endAddress: symbol->has_end_address ? symbol->end_address : 0]; } /** * Extract stack frame information from the crash log. Returns nil on error, or a PLCrashReportStackFrameInfo * instance on success. */ - (PLCrashReportStackFrameInfo *) extractStackFrameInfo: (Plcrash__CrashReport__Thread__StackFrame *) stackFrame error: (NSError **) outError { /* There should be at least one thread */ if (stackFrame == NULL) { populate_nserror(outError, PLCrashReporterErrorCrashReportInvalid, NSLocalizedString(@"Crash report is missing stack frame information", @"Missing stack frame info in crash report")); return nil; } PLCrashReportSymbolInfo *symbolInfo = nil; if (stackFrame->symbol != NULL) { if ((symbolInfo = [self extractSymbolInfo: stackFrame->symbol error: outError]) == NULL) return nil; } uint64_t instructionPointer = stackFrame->pc; /* * Workaround to handle incorrectly collected reports by old PLCrashReporter versions. * This guard does nothing on correctly collected reports. */ if (_machineInfo && _machineInfo.processorInfo.type == CPU_TYPE_ARM64 && _machineInfo.processorInfo.subtype == CPU_SUBTYPE_ARM64E) { instructionPointer &= ARM64_PTR_MASK; } return [[PLCrashReportStackFrameInfo alloc] initWithInstructionPointer: instructionPointer symbolInfo: symbolInfo]; } /** * Extract thread information from the crash log. Returns nil on error, or an array of PLCrashLogThreadInfo * instances on success. */ - (NSArray *) extractThreadInfo: (Plcrash__CrashReport *) crashReport error: (NSError **) outError { /* There should be at least one thread */ if (crashReport->n_threads == 0) { populate_nserror(outError, PLCrashReporterErrorCrashReportInvalid, NSLocalizedString(@"Crash report is missing thread state information", @"Missing thread info in crash report")); return nil; } /* Handle all threads */ NSMutableArray *threadResult = [NSMutableArray arrayWithCapacity: crashReport->n_threads]; for (size_t thr_idx = 0; thr_idx < crashReport->n_threads; thr_idx++) { Plcrash__CrashReport__Thread *thread = crashReport->threads[thr_idx]; /* Fetch stack frames for this thread */ NSMutableArray *frames = [NSMutableArray arrayWithCapacity: thread->n_frames]; for (size_t frame_idx = 0; frame_idx < thread->n_frames; frame_idx++) { Plcrash__CrashReport__Thread__StackFrame *frame = thread->frames[frame_idx]; PLCrashReportStackFrameInfo *frameInfo = [self extractStackFrameInfo: frame error: outError]; if (frameInfo == nil) return nil; [frames addObject: frameInfo]; } /* Fetch registers for this thread */ NSMutableArray *registers = [NSMutableArray arrayWithCapacity: thread->n_registers]; for (size_t reg_idx = 0; reg_idx < thread->n_registers; reg_idx++) { Plcrash__CrashReport__Thread__RegisterValue *reg = thread->registers[reg_idx]; PLCrashReportRegisterInfo *regInfo; /* Handle missing register name (should not occur!) */ if (reg->name == NULL) { populate_nserror(outError, PLCrashReporterErrorCrashReportInvalid, @"Missing register name in register value"); return nil; } regInfo = [[PLCrashReportRegisterInfo alloc] initWithRegisterName: [NSString stringWithUTF8String: reg->name] registerValue: reg->value]; [registers addObject: regInfo]; } /* Create the thread info instance */ PLCrashReportThreadInfo *threadInfo = [[PLCrashReportThreadInfo alloc] initWithThreadNumber: thread->thread_number stackFrames: frames crashed: thread->crashed registers: registers]; [threadResult addObject: threadInfo]; } return threadResult; } /** * Extract binary image information from the crash log. Returns nil on error. */ - (NSArray *) extractImageInfo: (Plcrash__CrashReport *) crashReport error: (NSError **) outError { /* There should be at least one image */ if (crashReport->n_binary_images == 0) { populate_nserror(outError, PLCrashReporterErrorCrashReportInvalid, NSLocalizedString(@"Crash report is missing binary image information", @"Missing image info in crash report")); return nil; } /* Handle all records */ NSMutableArray *images = [NSMutableArray arrayWithCapacity: crashReport->n_binary_images]; for (size_t i = 0; i < crashReport->n_binary_images; i++) { Plcrash__CrashReport__BinaryImage *image = crashReport->binary_images[i]; PLCrashReportBinaryImageInfo *imageInfo; /* Validate */ if (image->name == NULL) { populate_nserror(outError, PLCrashReporterErrorCrashReportInvalid, @"Missing image name in image record"); return nil; } /* Extract UUID value */ NSData *uuid = nil; if (image->uuid.len == 0) { /* No UUID */ uuid = nil; } else { uuid = [NSData dataWithBytes: image->uuid.data length: image->uuid.len]; } assert(image->uuid.len == 0 || uuid != nil); /* Extract code type (if available). */ PLCrashReportProcessorInfo *codeType = nil; if (image->code_type != NULL) { if ((codeType = [self extractProcessorInfo: image->code_type error: outError]) == nil) return nil; } imageInfo = [[PLCrashReportBinaryImageInfo alloc] initWithCodeType: codeType baseAddress: image->base_address size: image->size name: [NSString stringWithUTF8String: image->name] uuid: uuid]; [images addObject: imageInfo]; } return images; } /** * Extract exception information from the crash log. Returns nil on error. */ - (PLCrashReportExceptionInfo *) extractExceptionInfo: (Plcrash__CrashReport__Exception *) exceptionInfo error: (NSError **) outError { /* Validate */ if (exceptionInfo == NULL) { populate_nserror(outError, PLCrashReporterErrorCrashReportInvalid, NSLocalizedString(@"Crash report is missing Exception Information section", @"Missing appinfo in crash report")); return nil; } /* Name available? */ if (exceptionInfo->name == NULL) { populate_nserror(outError, PLCrashReporterErrorCrashReportInvalid, NSLocalizedString(@"Crash report is missing exception name field", @"Missing appinfo operating system in crash report")); return nil; } /* Reason available? */ if (exceptionInfo->reason == NULL) { populate_nserror(outError, PLCrashReporterErrorCrashReportInvalid, NSLocalizedString(@"Crash report is missing exception reason field", @"Missing appinfo operating system in crash report")); return nil; } /* Done */ NSString *name = [NSString stringWithUTF8String: exceptionInfo->name]; NSString *reason = [NSString stringWithUTF8String: exceptionInfo->reason]; /* Fetch stack frames for this thread */ NSMutableArray *frames = nil; if (exceptionInfo->n_frames > 0) { frames = [NSMutableArray arrayWithCapacity: exceptionInfo->n_frames]; for (size_t frame_idx = 0; frame_idx < exceptionInfo->n_frames; frame_idx++) { Plcrash__CrashReport__Thread__StackFrame *frame = exceptionInfo->frames[frame_idx]; PLCrashReportStackFrameInfo *frameInfo = [self extractStackFrameInfo: frame error: outError]; if (frameInfo == nil) return nil; [frames addObject: frameInfo]; } } if (frames == nil) { return [[PLCrashReportExceptionInfo alloc] initWithExceptionName: name reason: reason]; } else { return [[PLCrashReportExceptionInfo alloc] initWithExceptionName: name reason: reason stackFrames: frames]; } } /** * Extract signal information from the crash log. Returns nil on error. */ - (PLCrashReportSignalInfo *) extractSignalInfo: (Plcrash__CrashReport__Signal *) signalInfo error: (NSError **) outError { /* Validate */ if (signalInfo == NULL) { populate_nserror(outError, PLCrashReporterErrorCrashReportInvalid, NSLocalizedString(@"Crash report is missing Signal Information section", @"Missing appinfo in crash report")); return nil; } /* Name available? */ if (signalInfo->name == NULL) { populate_nserror(outError, PLCrashReporterErrorCrashReportInvalid, NSLocalizedString(@"Crash report is missing signal name field", @"Missing appinfo operating system in crash report")); return nil; } /* Code available? */ if (signalInfo->code == NULL) { populate_nserror(outError, PLCrashReporterErrorCrashReportInvalid, NSLocalizedString(@"Crash report is missing signal code field", @"Missing appinfo operating system in crash report")); return nil; } /* Done */ NSString *name = [NSString stringWithUTF8String: signalInfo->name]; NSString *code = [NSString stringWithUTF8String: signalInfo->code]; return [[PLCrashReportSignalInfo alloc] initWithSignalName: name code: code address: signalInfo->address]; } /** * Extract Mach exception information from the crash log. Returns nil on error. */ - (PLCrashReportMachExceptionInfo *) extractMachExceptionInfo: (Plcrash__CrashReport__Signal__MachException *) machExceptionInfo error: (NSError **) outError { /* Validate */ if (machExceptionInfo == NULL) { populate_nserror(outError, PLCrashReporterErrorCrashReportInvalid, NSLocalizedString(@"Crash report is missing Mach Exception Information section", @"Missing mach exception info in crash report")); return nil; } /* Sanity check; there should really only ever be 2 */ if (machExceptionInfo->n_codes > UINT8_MAX) { populate_nserror(outError, PLCrashReporterErrorCrashReportInvalid, NSLocalizedString(@"Crash report includes too many Mach Exception codes", @"Invalid mach exception info in crash report")); return nil; } /* Extract the codes */ NSMutableArray *codes = [NSMutableArray arrayWithCapacity: machExceptionInfo->n_codes]; for (size_t i = 0; i < machExceptionInfo->n_codes; i++) { [codes addObject: [NSNumber numberWithUnsignedLongLong: machExceptionInfo->codes[i]]]; } /* Done */ return [[PLCrashReportMachExceptionInfo alloc] initWithType: machExceptionInfo->type codes: codes]; } @end /** * @internal * Populate an NSError instance with the provided information. * * @param error Error instance to populate. If NULL, this method returns * and nothing is modified. * @param code The error code corresponding to this error. * @param description A localized error description. */ static void populate_nserror (NSError **error, PLCrashReporterError code, NSString *description) { NSDictionary *userInfo; if (error == NULL) return; /* Create the userInfo dictionary */ userInfo = [NSDictionary dictionaryWithObjectsAndKeys: description, NSLocalizedDescriptionKey, nil ]; *error = [NSError errorWithDomain: PLCrashReporterErrorDomain code: code userInfo: userInfo]; }