From 3d99ecb9cc61fbdd98e944cbbd44f9183f12b255 Mon Sep 17 00:00:00 2001 From: "landonf@OFFICE.PLAUSIBLELABS.COM" Date: Wed, 17 Dec 2008 08:24:39 +0000 Subject: [PATCH] Add basic crash log parsing support. git-svn-id: https://plcrashreporter.googlecode.com/svn/trunk@170 25172300-ee46-11dd-abe2-393a09110dd0 --- CrashReporter.xcodeproj/project.pbxproj | 51 +++++++++ Resources/CrashReporter.exp | 4 +- Source/CrashReporter.h | 8 ++ Source/PLCrashLog.h | 35 ++++++ Source/PLCrashLog.m | 137 +++++++++++++++++++++++- Source/PLCrashLogTests.m | 80 +++++++++++++- Source/PLCrashLogWriter.h | 7 -- Source/PLCrashLogWriter.m | 1 + Source/PLCrashLogWriterTests.m | 1 + 9 files changed, 313 insertions(+), 11 deletions(-) diff --git a/CrashReporter.xcodeproj/project.pbxproj b/CrashReporter.xcodeproj/project.pbxproj index 257f9d1..d6c98e3 100644 --- a/CrashReporter.xcodeproj/project.pbxproj +++ b/CrashReporter.xcodeproj/project.pbxproj @@ -140,6 +140,12 @@ 05F411AD0EF8DE68008050CF /* PLCrashLogTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 05F411AC0EF8DE68008050CF /* PLCrashLogTests.m */; }; 05F411AE0EF8DE68008050CF /* PLCrashLogTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 05F411AC0EF8DE68008050CF /* PLCrashLogTests.m */; }; 05F411AF0EF8DE68008050CF /* PLCrashLogTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 05F411AC0EF8DE68008050CF /* PLCrashLogTests.m */; }; + 05F411F30EF8DFD3008050CF /* crash_report.proto in Sources */ = {isa = PBXBuildFile; fileRef = 059670C70EEFAC3A008A0601 /* crash_report.proto */; }; + 05F411F40EF8DFDA008050CF /* crash_report.proto in Sources */ = {isa = PBXBuildFile; fileRef = 059670C70EEFAC3A008A0601 /* crash_report.proto */; }; + 05F411F50EF8DFE4008050CF /* crash_report.proto in Sources */ = {isa = PBXBuildFile; fileRef = 059670C70EEFAC3A008A0601 /* crash_report.proto */; }; + 05F411F70EF8E001008050CF /* protobuf-c.c in Sources */ = {isa = PBXBuildFile; fileRef = 05F40F830EF850FC008050CF /* protobuf-c.c */; }; + 05F411F90EF8E013008050CF /* protobuf-c.c in Sources */ = {isa = PBXBuildFile; fileRef = 05F40F830EF850FC008050CF /* protobuf-c.c */; }; + 05F411FB0EF8E023008050CF /* protobuf-c.c in Sources */ = {isa = PBXBuildFile; fileRef = 05F40F830EF850FC008050CF /* protobuf-c.c */; }; /* End PBXBuildFile section */ /* Begin PBXBuildRule section */ @@ -179,6 +185,42 @@ ); script = "cd \"${INPUT_FILE_DIR}\" && \"${SRCROOT}/Dependencies/protobuf-2.0.3/bin/protoc-c\" --c_out=\"${DERIVED_FILES_DIR}\" \"${INPUT_FILE_NAME}\""; }; + 05F411F10EF8DF79008050CF /* PBXBuildRule */ = { + isa = PBXBuildRule; + compilerSpec = com.apple.compilers.proxy.script; + filePatterns = "*.proto"; + fileType = pattern.proxy; + isEditable = 1; + outputFiles = ( + "$(INPUT_FILE_BASE).pb-c.c", + "$(INPUT_FILE_BASE).pb-c.h", + ); + script = "cd \"${INPUT_FILE_DIR}\" && \"${SRCROOT}/Dependencies/protobuf-2.0.3/bin/protoc-c\" --c_out=\"${DERIVED_FILES_DIR}\" \"${INPUT_FILE_NAME}\""; + }; + 05F411FE0EF8E070008050CF /* PBXBuildRule */ = { + isa = PBXBuildRule; + compilerSpec = com.apple.compilers.proxy.script; + filePatterns = "*.proto"; + fileType = pattern.proxy; + isEditable = 1; + outputFiles = ( + "$(INPUT_FILE_BASE).pb-c.c", + "$(INPUT_FILE_BASE).pb-c.h", + ); + script = "cd \"${INPUT_FILE_DIR}\" && \"${SRCROOT}/Dependencies/protobuf-2.0.3/bin/protoc-c\" --c_out=\"${DERIVED_FILES_DIR}\" \"${INPUT_FILE_NAME}\""; + }; + 05F411FF0EF8E070008050CF /* PBXBuildRule */ = { + isa = PBXBuildRule; + compilerSpec = com.apple.compilers.proxy.script; + filePatterns = "*.proto"; + fileType = pattern.proxy; + isEditable = 1; + outputFiles = ( + "$(INPUT_FILE_BASE).pb-c.c", + "$(INPUT_FILE_BASE).pb-c.h", + ); + script = "cd \"${INPUT_FILE_DIR}\" && \"${SRCROOT}/Dependencies/protobuf-2.0.3/bin/protoc-c\" --c_out=\"${DERIVED_FILES_DIR}\" \"${INPUT_FILE_NAME}\""; + }; /* End PBXBuildRule section */ /* Begin PBXContainerItemProxy section */ @@ -695,6 +737,7 @@ 05CD321E0EE93B59000FDE88 /* Create Framework */, ); buildRules = ( + 05F411FE0EF8E070008050CF /* PBXBuildRule */, ); dependencies = ( 05CD31720EE939DC000FDE88 /* PBXTargetDependency */, @@ -714,6 +757,7 @@ 05CD32200EE93B72000FDE88 /* Create Framework */, ); buildRules = ( + 05F411FF0EF8E070008050CF /* PBXBuildRule */, ); dependencies = ( 05CD31740EE939DF000FDE88 /* PBXTargetDependency */, @@ -812,6 +856,7 @@ 8DC2EF560486A6940098B216 /* Frameworks */, ); buildRules = ( + 05F411F10EF8DF79008050CF /* PBXBuildRule */, ); dependencies = ( ); @@ -966,6 +1011,8 @@ 05CD36D40EF25717000FDE88 /* PLCrashLogWriterEncoding.c in Sources */, 05F40ACC0EF7379F008050CF /* PLCrashReporter.m in Sources */, 05F411A90EF8DA31008050CF /* PLCrashLog.m in Sources */, + 05F411F40EF8DFDA008050CF /* crash_report.proto in Sources */, + 05F411FB0EF8E023008050CF /* protobuf-c.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -983,6 +1030,8 @@ 05CD36D20EF25717000FDE88 /* PLCrashLogWriterEncoding.c in Sources */, 05F40ACB0EF7379F008050CF /* PLCrashReporter.m in Sources */, 05F411A70EF8DA31008050CF /* PLCrashLog.m in Sources */, + 05F411F70EF8E001008050CF /* protobuf-c.c in Sources */, + 05F411F50EF8DFE4008050CF /* crash_report.proto in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1081,6 +1130,8 @@ 05CD36D60EF25717000FDE88 /* PLCrashLogWriterEncoding.c in Sources */, 05F40ACD0EF7379F008050CF /* PLCrashReporter.m in Sources */, 05F411AB0EF8DA31008050CF /* PLCrashLog.m in Sources */, + 05F411F30EF8DFD3008050CF /* crash_report.proto in Sources */, + 05F411F90EF8E013008050CF /* protobuf-c.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Resources/CrashReporter.exp b/Resources/CrashReporter.exp index 88e44e2..8641894 100755 --- a/Resources/CrashReporter.exp +++ b/Resources/CrashReporter.exp @@ -4,8 +4,10 @@ # Created by Landon Fuller on 12/5/08. # Copyright 2008 Plausible Labs Cooperative, Inc. All rights reserved. -# CrashReporter +# PLCrashReporter _PLCrashReporterException _PLCrashReporterErrorDomain .objc_class_name_PLCrashReporter +# PLCrashLog +.objc_class_name_PLCrashLog diff --git a/Source/CrashReporter.h b/Source/CrashReporter.h index a0da48c..299582b 100644 --- a/Source/CrashReporter.h +++ b/Source/CrashReporter.h @@ -15,11 +15,16 @@ #endif #import "PLCrashReporter.h" +#import "PLCrashLog.h" /** * @defgroup functions Crash Reporter Functions Reference */ +/** + * @defgroup types Crash Reporter Data Types Reference + */ + /** * @defgroup constants Crash Reporter Constants Reference */ @@ -61,6 +66,9 @@ typedef enum { /** An Mach or POSIX operating system error has occured. The underlying NSError cause may be fetched from the userInfo * dictionary using the NSUnderlyingErrorKey key. */ PLCrashReporterErrorOperatingSystem = 1, + + /** The crash report log file is corrupt or invalid */ + PLCrashReporterErrorCrashReportInvalid = 2, } PLCrashReporterError; diff --git a/Source/PLCrashLog.h b/Source/PLCrashLog.h index 397bc2b..de26552 100644 --- a/Source/PLCrashLog.h +++ b/Source/PLCrashLog.h @@ -6,10 +6,45 @@ */ #import +/** + * @ingroup constants + * Crash file magic identifier */ +#define PLCRASH_LOG_FILE_MAGIC "plcrash" +/** + * @ingroup constants + * Crash format version byte identifier. Will not change outside of the introduction of + * an entirely new crash log format. */ +#define PLCRASH_LOG_FILE_VERSION 1 + +/** + * @ingroup types + * Plausible Crash Log Header. + */ +struct PLCrashLogFileHeader { + /** File magic, not NULL terminated */ + const char magic[7]; + + /** File version */ + const uint8_t version; + + /** File data */ + const uint8_t data[]; +} __attribute__((packed)); + + +/** + * @internal + * Private decoder instance variables (used to hide the underlying protobuf parser). + */ +typedef struct _PLCrashLogDecoder _PLCrashLogDecoder; @interface PLCrashLog : NSObject { @private + /** Private implementation variables (used to hide the underlying protobuf parser) */ + _PLCrashLogDecoder *_decoder; } +- (id) initWithData: (NSData *) encodedData error: (NSError **) outError; + @end diff --git a/Source/PLCrashLog.m b/Source/PLCrashLog.m index 061ce7e..aa3b33f 100644 --- a/Source/PLCrashLog.m +++ b/Source/PLCrashLog.m @@ -6,6 +6,23 @@ */ #import "PLCrashLog.h" +#import "CrashReporter.h" + +#import "crash_report.pb-c.h" + +struct _PLCrashLogDecoder { + Plcrash__CrashReport *crashReport; +}; + +@interface PLCrashLog (PrivateMethods) + +- (void) populateError: (NSError **) error + errorCode: (PLCrashReporterError) code + description: (NSString *) description; + +- (Plcrash__CrashReport *) decodeCrashData: (NSData *) data error: (NSError **) outError; + +@end /** * Provides decoding of crash logs generated by the PLCrashReporter framework. @@ -29,13 +46,131 @@ if ((self = [super init]) == nil) return nil; + + /* Allocate the struct and attempt to parse */ + _decoder = malloc(sizeof(_PLCrashLogDecoder)); + _decoder->crashReport = [self decodeCrashData: encodedData error: outError]; + + if (_decoder->crashReport == NULL) { + [self release]; + return nil; + } + return self; } - (void) dealloc { + /* Free the decoder state */ + if (_decoder != NULL) { + if (_decoder->crashReport != NULL) { + protobuf_c_message_free_unpacked((ProtobufCMessage *) _decoder->crashReport, &protobuf_c_system_allocator); + } + + free(_decoder); + } [super dealloc]; } - +#if 0 +- (void) unpack { + /* Check the file magic. The file must be large enough for the value + version + data */ + STAssertTrue(statbuf.st_size > strlen(PLCRASH_LOG_FILE_MAGIC) + sizeof(uint8_t), @"File is too small for magic + version + data"); + STAssertTrue(memcmp(buf, PLCRASH_LOG_FILE_MAGIC, strlen(PLCRASH_LOG_FILE_MAGIC)) == 0, @"File does not start with magic string"); + STAssertEquals(((uint8_t *) buf)[strlen(PLCRASH_LOG_FILE_MAGIC)], (uint8_t)PLCRASH_LOG_FILE_VERSION, @"File version is not equal to 0"); + + /* Try to read the crash report */ + Plcrash__CrashReport *crashReport; + crashReport = plcrash__crash_report__unpack(&protobuf_c_system_allocator, statbuf.st_size, buf + strlen(PLCRASH_LOG_FILE_MAGIC) + sizeof(uint8_t)); + + /* If reading the report didn't fail, test the contents */ + STAssertNotNULL(crashReport, @"Could not decode crash report"); + if (crashReport != NULL) { + /* Test the report */ + [self checkSystemInfo: crashReport]; + [self checkThreads: crashReport]; + [self checkException: crashReport]; + + /* Free it */ + protobuf_c_message_free_unpacked((ProtobufCMessage *) crashReport, &protobuf_c_system_allocator); + } +} +#endif @end + + +/** + * @internal + * Private Methods + */ +@implementation PLCrashLog (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 PLCrashLogFileHeader *header; + const void *bytes; + + bytes = [data bytes]; + header = bytes; + + /* Verify that the crash log is sufficently large */ + if (sizeof(struct PLCrashLogFileHeader) >= [data length]) { + [self populateError: outError errorCode: PLCrashReporterErrorCrashReportInvalid description: NSLocalizedString(@"Could not decode truncated crash log", @"Crash log decoding error message")]; + return NULL; + } + + /* Check the file magic */ + if (memcmp(header->magic, PLCRASH_LOG_FILE_MAGIC, strlen(PLCRASH_LOG_FILE_MAGIC)) != 0) { + [self populateError: outError errorCode: PLCrashReporterErrorCrashReportInvalid description: NSLocalizedString(@"Could not decode invalid crash log header", @"Crash log decoding error message")]; + return NULL; + } + + /* Check the version */ + if(header->version != PLCRASH_LOG_FILE_VERSION) { + [self populateError: outError errorCode: PLCrashReporterErrorCrashReportInvalid description: [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(&protobuf_c_system_allocator, [data length] - sizeof(struct PLCrashLogFileHeader), header->data); + if (crashReport == NULL) { + [self populateError: outError errorCode: PLCrashReporterErrorCrashReportInvalid description: NSLocalizedString(@"An unknown error occured decoding the crash report", @"Crash log decoding error message")]; + return NULL; + } + + return crashReport; +} + + +/** + * 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. + * @param cause The underlying cause, if any. May be nil. + */ +- (void) populateError: (NSError **) error + errorCode: (PLCrashReporterError) code + description: (NSString *) description +{ + NSMutableDictionary *userInfo; + + if (error == NULL) + return; + + /* Create the userInfo dictionary */ + userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + description, NSLocalizedDescriptionKey, + nil + ]; + + *error = [NSError errorWithDomain: PLCrashReporterErrorDomain code: code userInfo: userInfo]; +} + +@end \ No newline at end of file diff --git a/Source/PLCrashLogTests.m b/Source/PLCrashLogTests.m index eb93380..72d61b6 100644 --- a/Source/PLCrashLogTests.m +++ b/Source/PLCrashLogTests.m @@ -6,14 +6,90 @@ */ #import "GTMSenTestCase.h" +#import "PLCrashLog.h" #import "PLCrashReporter.h" +#import "PLCrashFrameWalker.h" +#import "PLCrashLogWriter.h" + +#import + +@interface PLCrashLogTests : SenTestCase { +@private + /* Path to crash log */ + NSString *_logPath; + + /* Test thread */ + plframe_test_thead_t _thr_args; +} -@interface PLCrashLogTests : SenTestCase @end @implementation PLCrashLogTests -- (void) testSomething { +- (void) setUp { + /* Create a temporary log path */ + _logPath = [[NSTemporaryDirectory() stringByAppendingString: [[NSProcessInfo processInfo] globallyUniqueString]] retain]; + + /* Create the test thread */ + plframe_test_thread_spawn(&_thr_args); } +- (void) tearDown { + NSError *error; + + /* Delete the file */ + STAssertTrue([[NSFileManager defaultManager] removeItemAtPath: _logPath error: &error], @"Could not remove log file"); + [_logPath release]; + + /* Stop the test thread */ + plframe_test_thread_stop(&_thr_args); +} + +- (void) testWriteReport { + siginfo_t info; + plframe_cursor_t cursor; + plcrash_log_writer_t writer; + plcrash_async_file_t file; + NSError *error; + + /* Initialze faux crash data */ + { + info.si_addr = 0x0; + info.si_errno = 0; + info.si_pid = getpid(); + info.si_uid = getuid(); + info.si_code = SEGV_MAPERR; + info.si_signo = SIGSEGV; + info.si_status = 0; + + /* Steal the test thread's stack for iteration */ + plframe_cursor_thread_init(&cursor, pthread_mach_thread_np(_thr_args.thread)); + } + + /* Open the output file */ + int fd = open([_logPath UTF8String], O_RDWR|O_CREAT|O_EXCL, 0644); + plcrash_async_file_init(&file, fd, 0); + + /* Initialize a writer */ + STAssertEquals(PLCRASH_ESUCCESS, plcrash_log_writer_init(&writer, @"test.id", @"1.0"), @"Initialization failed"); + + /* Set an exception */ + plcrash_log_writer_set_exception(&writer, [NSException exceptionWithName: @"TestException" reason: @"TestReason" userInfo: nil]); + + /* Write the crash report */ + STAssertEquals(PLCRASH_ESUCCESS, plcrash_log_writer_write(&writer, &file, &info, cursor.uap), @"Crash log failed"); + + /* Close it */ + plcrash_log_writer_close(&writer); + plcrash_log_writer_free(&writer); + + plcrash_async_file_flush(&file); + plcrash_async_file_close(&file); + + /* Try to parse it */ + PLCrashLog *crashLog = [[[PLCrashLog alloc] initWithData: [NSData dataWithContentsOfMappedFile: _logPath] error: &error] autorelease]; + STAssertNotNil(crashLog, @"Could not decode crash log: %@", error); +} + + @end diff --git a/Source/PLCrashLogWriter.h b/Source/PLCrashLogWriter.h index 4629129..ff09b36 100644 --- a/Source/PLCrashLogWriter.h +++ b/Source/PLCrashLogWriter.h @@ -21,13 +21,6 @@ * @{ */ -/** Crash file magic identifier */ -#define PLCRASH_LOG_FILE_MAGIC "plcrash" - -/** Crash format version byte identifier. Will not change outside of the introduction of - * an entirely new crash log format. */ -#define PLCRASH_LOG_FILE_VERSION 1 - /** CrashReport machine type enums */ enum { PLCRASH_MACHINE_TYPE_X86_32 = 0, diff --git a/Source/PLCrashLogWriter.m b/Source/PLCrashLogWriter.m index 52373e0..ed24b94 100644 --- a/Source/PLCrashLogWriter.m +++ b/Source/PLCrashLogWriter.m @@ -16,6 +16,7 @@ #import +#import "PLCrashLog.h" #import "PLCrashLogWriter.h" #import "PLCrashLogWriterEncoding.h" #import "PLCrashAsync.h" diff --git a/Source/PLCrashLogWriterTests.m b/Source/PLCrashLogWriterTests.m index 45ba73b..7ccad44 100644 --- a/Source/PLCrashLogWriterTests.m +++ b/Source/PLCrashLogWriterTests.m @@ -7,6 +7,7 @@ #import "GTMSenTestCase.h" +#import "PLCrashLog.h" #import "PLCrashLogWriter.h" #import "PLCrashFrameWalker.h"