diff --git a/Bugsnag/Helpers/BSGKeys.h b/Bugsnag/Helpers/BSGKeys.h index 82b6d4ce3..2ef1545c9 100644 --- a/Bugsnag/Helpers/BSGKeys.h +++ b/Bugsnag/Helpers/BSGKeys.h @@ -53,11 +53,8 @@ static BSGKey const BSGKeyGroupingHash = @"groupingHash"; static BSGKey const BSGKeyHandled = @"handled"; static BSGKey const BSGKeyHandledCount = @"handledCount"; static BSGKey const BSGKeyId = @"id"; -static BSGKey const BSGKeyImageAddress = @"image_addr"; -static BSGKey const BSGKeyImageVmAddress = @"image_vmaddr"; static BSGKey const BSGKeyIncomplete = @"incomplete"; static BSGKey const BSGKeyInfo = @"info"; -static BSGKey const BSGKeyInstructionAddress = @"instruction_addr"; static BSGKey const BSGKeyIsLaunching = @"isLaunching"; static BSGKey const BSGKeyIsLR = @"isLR"; static BSGKey const BSGKeyIsPC = @"isPC"; @@ -80,8 +77,6 @@ static BSGKey const BSGKeyMethod = @"method"; static BSGKey const BSGKeyName = @"name"; static BSGKey const BSGKeyNotifier = @"notifier"; static BSGKey const BSGKeyNotifyEndpoint = @"notify"; -static BSGKey const BSGKeyObjectAddress = @"object_addr"; -static BSGKey const BSGKeyObjectName = @"object_name"; static BSGKey const BSGKeyOrientation = @"orientation"; static BSGKey const BSGKeyOsVersion = @"osVersion"; static BSGKey const BSGKeyPayloadVersion = @"payloadVersion"; @@ -100,8 +95,6 @@ static BSGKey const BSGKeySignal = @"signal"; static BSGKey const BSGKeyStacktrace = @"stacktrace"; static BSGKey const BSGKeyStartedAt = @"startedAt"; static BSGKey const BSGKeySymbolAddr = @"symbolAddress"; -static BSGKey const BSGKeySymbolAddress = @"symbol_addr"; -static BSGKey const BSGKeySymbolName = @"symbol_name"; static BSGKey const BSGKeySystem = @"system"; static BSGKey const BSGKeyThermalState = @"thermalState"; static BSGKey const BSGKeyThreads = @"threads"; diff --git a/Bugsnag/Payload/BugsnagEvent.m b/Bugsnag/Payload/BugsnagEvent.m index 97cfbdf3c..541ffcada 100644 --- a/Bugsnag/Payload/BugsnagEvent.m +++ b/Bugsnag/Payload/BugsnagEvent.m @@ -314,7 +314,7 @@ - (instancetype)initWithKSCrashReport:(NSDictionary *)event { // generate threads/error info NSArray *binaryImages = event[@"binary_images"]; NSArray *threadDict = [event valueForKeyPath:@"crash.threads"]; - NSArray *threads = [BugsnagThread threadsFromArray:threadDict binaryImages:binaryImages depth:depth errorType:errorType]; + NSArray *threads = [BugsnagThread threadsFromArray:threadDict binaryImages:binaryImages]; BugsnagThread *errorReportingThread = nil; for (BugsnagThread *thread in threads) { diff --git a/Bugsnag/Payload/BugsnagStackframe+Private.h b/Bugsnag/Payload/BugsnagStackframe+Private.h index b1142634d..217bc3f5f 100644 --- a/Bugsnag/Payload/BugsnagStackframe+Private.h +++ b/Bugsnag/Payload/BugsnagStackframe+Private.h @@ -14,7 +14,7 @@ NS_ASSUME_NONNULL_BEGIN + (NSArray *)stackframesWithBacktrace:(uintptr_t *)backtrace length:(NSUInteger)length; -/// Constructs a stackframe object from a stackframe dictionary and list of images captured by KSCrash. +/// Constructs a stackframe object from a KSCrashReport backtrace dictionary. + (nullable instancetype)frameFromDict:(NSDictionary *)dict withImages:(NSArray *> *)binaryImages; /// Constructs a stackframe object from a JSON object (typically loaded from disk.) diff --git a/Bugsnag/Payload/BugsnagStackframe.m b/Bugsnag/Payload/BugsnagStackframe.m index 740c05683..8f3968646 100644 --- a/Bugsnag/Payload/BugsnagStackframe.m +++ b/Bugsnag/Payload/BugsnagStackframe.m @@ -10,6 +10,7 @@ #import "BSGKeys.h" #import "BSG_KSBacktrace.h" +#import "BSG_KSCrashReportFields.h" #import "BSG_KSMachHeaders.h" #import "BSG_Symbolicate.h" #import "BugsnagCollections.h" @@ -29,7 +30,7 @@ @implementation BugsnagStackframe static NSDictionary * _Nullable FindImage(NSArray *images, uintptr_t addr) { for (NSDictionary *image in images) { - if ([(NSNumber *)image[BSGKeyImageAddress] unsignedLongValue] == addr) { + if ([(NSNumber *)image[@ BSG_KSCrashField_ImageAddress] unsignedLongValue] == addr) { return image; } } @@ -65,7 +66,7 @@ + (NSNumber *)readInt:(NSDictionary *)json key:(NSString *)key { } + (instancetype)frameFromDict:(NSDictionary *)dict withImages:(NSArray *> *)binaryImages { - NSNumber *frameAddress = dict[BSGKeyInstructionAddress]; + NSNumber *frameAddress = dict[@ BSG_KSCrashField_InstructionAddr]; if (frameAddress.unsignedLongLongValue == 1) { // We sometimes get a frame address of 0x1 at the bottom of the call stack. // It's not a valid stack frame and causes E2E tests to fail, so should be ignored. @@ -74,18 +75,18 @@ + (instancetype)frameFromDict:(NSDictionary *)dict withImages:(N BugsnagStackframe *frame = [BugsnagStackframe new]; frame.frameAddress = frameAddress; - frame.symbolAddress = dict[BSGKeySymbolAddress]; - frame.machoLoadAddress = dict[BSGKeyObjectAddress]; - frame.machoFile = dict[BSGKeyObjectName]; - frame.method = dict[BSGKeySymbolName]; + frame.machoFile = dict[@ BSG_KSCrashField_ObjectName]; // last path component + frame.machoLoadAddress = dict[@ BSG_KSCrashField_ObjectAddr]; + frame.method = dict[@ BSG_KSCrashField_SymbolName]; + frame.symbolAddress = dict[@ BSG_KSCrashField_SymbolAddr]; frame.isPc = [dict[BSGKeyIsPC] boolValue]; frame.isLr = [dict[BSGKeyIsLR] boolValue]; NSDictionary *image = FindImage(binaryImages, (uintptr_t)frame.machoLoadAddress.unsignedLongLongValue); if (image != nil) { - frame.machoUuid = image[BSGKeyUuid]; - frame.machoVmAddress = image[BSGKeyImageVmAddress]; - frame.machoFile = image[BSGKeyName]; + frame.machoFile = image[@ BSG_KSCrashField_Name]; // full path + frame.machoUuid = image[@ BSG_KSCrashField_UUID]; + frame.machoVmAddress = image[@ BSG_KSCrashField_ImageVmAddress]; } else if (frame.isPc) { // If the program counter's value isn't in any known image, the crash may have been due to a bad function pointer. // Ignore these frames to prevent the dashboard grouping on the address. @@ -112,7 +113,7 @@ + (instancetype)frameFromDict:(NSDictionary *)dict withImages:(N continue; } - [frames addObject:[[BugsnagStackframe alloc] initWithAddress:address isPc:i == 0]]; + [frames addObject:[[BugsnagStackframe alloc] initWithAddress:address]]; } return frames; @@ -156,7 +157,6 @@ + (instancetype)frameFromDict:(NSDictionary *)dict withImages:(N if (match.numberOfRanges != 6) { continue; } - NSString *frameNumber = [string substringWithRange:[match rangeAtIndex:1]]; NSString *imageName = [string substringWithRange:[match rangeAtIndex:2]]; NSString *frameAddress = [string substringWithRange:[match rangeAtIndex:3]]; NSRange symbolNameRange = [match rangeAtIndex:5]; @@ -170,7 +170,7 @@ + (instancetype)frameFromDict:(NSDictionary *)dict withImages:(N sscanf(frameAddress.UTF8String, "%lx", &address); } - BugsnagStackframe *frame = [[BugsnagStackframe alloc] initWithAddress:address isPc:[frameNumber isEqualToString:@"0"]]; + BugsnagStackframe *frame = [[BugsnagStackframe alloc] initWithAddress:address]; frame.machoFile = imageName; frame.method = symbolName ?: frameAddress; [frames addObject:frame]; @@ -179,10 +179,9 @@ + (instancetype)frameFromDict:(NSDictionary *)dict withImages:(N return [NSArray arrayWithArray:frames]; } -- (instancetype)initWithAddress:(uintptr_t)address isPc:(BOOL)isPc { +- (instancetype)initWithAddress:(uintptr_t)address { if ((self = [super init])) { _frameAddress = @(address); - _isPc = isPc; _needsSymbolication = YES; BSG_Mach_Header_Info *header = bsg_mach_headers_image_at_address(address); if (header) { @@ -228,12 +227,8 @@ - (NSDictionary *)toDictionary { dict[BSGKeySymbolAddr] = FormatMemoryAddress(self.symbolAddress); dict[BSGKeyMachoLoadAddr] = FormatMemoryAddress(self.machoLoadAddress); dict[BSGKeyMachoVMAddress] = FormatMemoryAddress(self.machoVmAddress); - if (self.isPc) { - dict[BSGKeyIsPC] = @(self.isPc); - } - if (self.isLr) { - dict[BSGKeyIsLR] = @(self.isLr); - } + dict[BSGKeyIsPC] = self.isPc ? @YES : nil; + dict[BSGKeyIsLR] = self.isLr ? @YES : nil; dict[BSGKeyType] = self.type; dict[@"codeIdentifier"] = self.codeIdentifier; dict[@"columnNumber"] = self.columnNumber; diff --git a/Bugsnag/Payload/BugsnagThread+Private.h b/Bugsnag/Payload/BugsnagThread+Private.h index 7cbda821b..23118f650 100644 --- a/Bugsnag/Payload/BugsnagThread+Private.h +++ b/Bugsnag/Payload/BugsnagThread+Private.h @@ -30,9 +30,7 @@ NS_ASSUME_NONNULL_BEGIN @property (readwrite, nonatomic) BOOL errorReportingThread; -+ (NSDictionary *)enhanceThreadInfo:(NSDictionary *)thread - depth:(NSUInteger)depth - errorType:(nullable NSString *)errorType; ++ (NSDictionary *)enhanceThreadInfo:(NSDictionary *)thread; #if BSG_HAVE_MACH_THREADS + (nullable instancetype)mainThread; @@ -40,10 +38,7 @@ NS_ASSUME_NONNULL_BEGIN + (NSMutableArray *)serializeThreads:(nullable NSArray *)threads; -+ (NSMutableArray *)threadsFromArray:(NSArray *)threads - binaryImages:(NSArray *)binaryImages - depth:(NSUInteger)depth - errorType:(nullable NSString *)errorType; ++ (NSMutableArray *)threadsFromArray:(NSArray *)threads binaryImages:(NSArray *)binaryImages; - (NSDictionary *)toDictionary; diff --git a/Bugsnag/Payload/BugsnagThread.m b/Bugsnag/Payload/BugsnagThread.m index 5193412b6..9e8ccab72 100644 --- a/Bugsnag/Payload/BugsnagThread.m +++ b/Bugsnag/Payload/BugsnagThread.m @@ -150,14 +150,11 @@ + (NSMutableArray *)serializeThreads:(NSArray *)threads { /** * Deerializes Bugsnag Threads from a KSCrash report */ -+ (NSMutableArray *)threadsFromArray:(NSArray *)threads - binaryImages:(NSArray *)binaryImages - depth:(NSUInteger)depth - errorType:(NSString *)errorType { ++ (NSMutableArray *)threadsFromArray:(NSArray *)threads binaryImages:(NSArray *)binaryImages { NSMutableArray *bugsnagThreads = [NSMutableArray new]; for (NSDictionary *thread in threads) { - NSDictionary *threadInfo = [self enhanceThreadInfo:thread depth:depth errorType:errorType]; + NSDictionary *threadInfo = [self enhanceThreadInfo:thread]; BugsnagThread *obj = [[BugsnagThread alloc] initWithThread:threadInfo binaryImages:binaryImages]; [bugsnagThreads addObject:obj]; } @@ -165,42 +162,39 @@ + (NSMutableArray *)serializeThreads:(NSArray *)threads { } /** - * Enhances the thread information recorded by KSCrash. Specifically, this will trim the error reporting thread frames - * by the `depth` configured, and add information to each frame indicating whether they - * are within the program counter/link register. - * - * The error reporting thread is the thread on which the error occurred, and is given more - * prominence in the Bugsnag Dashboard - therefore we enhance it with extra info. - * - * @param thread the captured thread - * @param depth the 'depth'. This is equivalent to the number of frames which should be discarded from a report, - * and is configurable by the user. - * @param errorType the type of error as recorded by KSCrash (e.g. mach, signal) - * @return the enhanced thread information + * Adds isPC and isLR values to a KSCrashReport thread dictionary. */ -+ (NSDictionary *)enhanceThreadInfo:(NSDictionary *)thread - depth:(NSUInteger)depth - errorType:(NSString *)errorType { ++ (NSDictionary *)enhanceThreadInfo:(NSDictionary *)thread { NSArray *backtrace = thread[@"backtrace"][@"contents"]; BOOL isReportingThread = [thread[@"crashed"] boolValue]; if (isReportingThread) { - BOOL stackOverflow = [thread[@"stack"][@"overflow"] boolValue]; - NSUInteger seen = 0; + NSDictionary *registers = thread[@ BSG_KSCrashField_Registers][@ BSG_KSCrashField_Basic]; +#if TARGET_CPU_ARM || TARGET_CPU_ARM64 + NSNumber *pc = registers[@"pc"]; + NSNumber *lr = registers[@"lr"]; +#elif TARGET_CPU_X86 + NSNumber *pc = registers[@"eip"]; + NSNumber *lr = nil; +#elif TARGET_CPU_X86_64 + NSNumber *pc = registers[@"rip"]; + NSNumber *lr = nil; +#else +#error Unsupported CPU architecture +#endif + NSMutableArray *stacktrace = [NSMutableArray array]; for (NSDictionary *frame in backtrace) { NSMutableDictionary *mutableFrame = [frame mutableCopy]; - if (seen++ >= depth) { - // Mark the frame so we know where it came from - if (seen == 1 && !stackOverflow) { - mutableFrame[BSGKeyIsPC] = @YES; - } - if (seen == 2 && !stackOverflow && [@[BSGKeySignal, BSGKeyMach] containsObject:errorType]) { - mutableFrame[BSGKeyIsLR] = @YES; - } - [stacktrace addObject:mutableFrame]; + NSNumber *instructionAddress = frame[@ BSG_KSCrashField_InstructionAddr]; + if ([instructionAddress isEqual:pc]) { + mutableFrame[BSGKeyIsPC] = @YES; + } + if ([instructionAddress isEqual:lr]) { + mutableFrame[BSGKeyIsLR] = @YES; } + [stacktrace addObject:mutableFrame]; } NSMutableDictionary *mutableBacktrace = [thread[@"backtrace"] mutableCopy]; mutableBacktrace[@"contents"] = stacktrace; diff --git a/Bugsnag/include/Bugsnag/BugsnagStackframe.h b/Bugsnag/include/Bugsnag/BugsnagStackframe.h index 18c057956..dff071d1d 100644 --- a/Bugsnag/include/Bugsnag/BugsnagStackframe.h +++ b/Bugsnag/include/Bugsnag/BugsnagStackframe.h @@ -43,7 +43,7 @@ BUGSNAG_EXTERN @property (strong, nullable, nonatomic) NSNumber *frameAddress; /** - * The VM address of the Mach-O file + * The Mach-O file's desired base virtual memory address */ @property (strong, nullable, nonatomic) NSNumber *machoVmAddress; @@ -53,17 +53,17 @@ BUGSNAG_EXTERN @property (strong, nullable, nonatomic) NSNumber *symbolAddress; /** - * The load address of the Mach-O file + * The address at which the Mach-O file is mapped into memory */ @property (strong, nullable, nonatomic) NSNumber *machoLoadAddress; /** - * Whether the frame was within the program counter + * True if `frameAddress` is equal to the value of the program counter register. */ @property (nonatomic) BOOL isPc; /** - * Whether the frame was within the link register + * True if `frameAddress` is equal to the value of the link register. */ @property (nonatomic) BOOL isLr; diff --git a/CHANGELOG.md b/CHANGELOG.md index ea18dc2c0..acf9554ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ Changelog ========= +## TBD + +### Bug fixes + +* Fix accuracy of `isLR` and `isPC` stack frame values. + [#1470](https://github.com/bugsnag/bugsnag-cocoa/pull/1470) + ## 6.22.2 (2022-08-17) ### Bug fixes diff --git a/Tests/BugsnagTests/BugsnagErrorTest.m b/Tests/BugsnagTests/BugsnagErrorTest.m index ce9e279b8..1662f9685 100644 --- a/Tests/BugsnagTests/BugsnagErrorTest.m +++ b/Tests/BugsnagTests/BugsnagErrorTest.m @@ -111,9 +111,7 @@ - (BugsnagThread *)findErrorReportingThread:(NSDictionary *)event { NSArray *binaryImages = event[@"binary_images"]; NSArray *threadDict = [event valueForKeyPath:@"crash.threads"]; NSArray *threads = [BugsnagThread threadsFromArray:threadDict - binaryImages:binaryImages - depth:0 - errorType:@"user"]; + binaryImages:binaryImages]; for (BugsnagThread *thread in threads) { if (thread.errorReportingThread) { return thread; diff --git a/Tests/BugsnagTests/BugsnagStackframeTest.m b/Tests/BugsnagTests/BugsnagStackframeTest.m index e92d328ae..c647c2385 100644 --- a/Tests/BugsnagTests/BugsnagStackframeTest.m +++ b/Tests/BugsnagTests/BugsnagStackframeTest.m @@ -238,7 +238,6 @@ - (void)testRealCallStackSymbols { NSArray *callStackSymbols = [NSThread callStackSymbols]; NSArray *stackframes = [BugsnagStackframe stackframesWithCallStackSymbols:callStackSymbols]; XCTAssertEqual(stackframes.count, callStackSymbols.count, @"All valid stack frame strings should be parsed"); - XCTAssertTrue(stackframes.firstObject.isPc, @"The first stack frame should have isPc set to true"); BOOL __block didSeeMain = NO; [stackframes enumerateObjectsUsingBlock:^(BugsnagStackframe *stackframe, NSUInteger idx, BOOL *stop) { XCTAssertNotNil(stackframe.frameAddress); diff --git a/Tests/BugsnagTests/BugsnagThreadTests.m b/Tests/BugsnagTests/BugsnagThreadTests.m index 1e58284b8..6f664aa1f 100644 --- a/Tests/BugsnagTests/BugsnagThreadTests.m +++ b/Tests/BugsnagTests/BugsnagThreadTests.m @@ -98,37 +98,30 @@ - (void)testThreadEnhancementNotCrashed { NSDictionary *dict = @{ @"backtrace": @{ @"contents": @[ - @{}, - @{}, - @{}, + @{@"instruction_addr": @304}, + @{@"instruction_addr": @204}, + @{@"instruction_addr": @104} ] }, - @"crashed": @NO + @"registers": @{ + @"basic": @{ +#if TARGET_CPU_ARM || TARGET_CPU_ARM64 + @"pc": @304, + @"lr": @204 +#elif TARGET_CPU_X86 + @"eip": @304 +#elif TARGET_CPU_X86_64 + @"rip": @304 +#else +#error Unsupported CPU architecture +#endif + } + } }; - NSDictionary *thread = [BugsnagThread enhanceThreadInfo:dict depth:0 errorType:nil]; + NSDictionary *thread = [BugsnagThread enhanceThreadInfo:dict]; XCTAssertEqual(dict, thread); } -/** - * Dictionary info enhanced if an error reporting thread - */ -- (void)testThreadEnhancementCrashed { - NSDictionary *dict = @{ - @"backtrace": @{ - @"contents": @[ - @{}, - @{}, - @{}, - ] - }, - @"crashed": @YES - }; - NSDictionary *thread = [BugsnagThread enhanceThreadInfo:dict depth:0 errorType:nil]; - XCTAssertNotEqual(dict, thread); - NSArray *trace = thread[@"backtrace"][@"contents"]; - XCTAssertEqual(3, [trace count]); -} - /** * Frames enhanced with lc/pr info */ @@ -136,74 +129,44 @@ - (void)testThreadEnhancementLcPr { NSDictionary *dict = @{ @"backtrace": @{ @"contents": @[ - @{}, - @{}, - @{} + @{@"instruction_addr": @304}, + @{@"instruction_addr": @204}, + @{@"instruction_addr": @104} ] }, - @"crashed": @YES + @"crashed": @YES, + @"registers": @{ + @"basic": @{ +#if TARGET_CPU_ARM || TARGET_CPU_ARM64 + @"pc": @304, + @"lr": @204 +#elif TARGET_CPU_X86 + @"eip": @304 +#elif TARGET_CPU_X86_64 + @"rip": @304 +#else +#error Unsupported CPU architecture +#endif + } + } }; - NSDictionary *thread = [BugsnagThread enhanceThreadInfo:dict depth:0 errorType:@"signal"]; + NSDictionary *thread = [BugsnagThread enhanceThreadInfo:dict]; XCTAssertNotEqual(dict, thread); NSArray *trace = thread[@"backtrace"][@"contents"]; XCTAssertEqual(3, [trace count]); - XCTAssertEqual(1, [trace[0] count]); - XCTAssertTrue(trace[0][@"isPC"]); - XCTAssertEqual(1, [trace[1] count]); - XCTAssertTrue(trace[1][@"isLR"]); - XCTAssertEqual(0, [trace[2] count]); -} + XCTAssertEqualObjects(trace[0][@"isPC"], @YES); + XCTAssertEqualObjects(trace[0][@"isLR"], nil); -/** - * Frames enhanced with lc/pr info, wrong error type - */ -- (void)testThreadEnhancementWrongErrorType { - NSDictionary *dict = @{ - @"backtrace": @{ - @"contents": @[ - @{}, - @{}, - @{} - ] - }, - @"crashed": @YES - }; - NSDictionary *thread = [BugsnagThread enhanceThreadInfo:dict depth:0 errorType:@"NSException"]; - XCTAssertNotEqual(dict, thread); - NSArray *trace = thread[@"backtrace"][@"contents"]; - XCTAssertEqual(3, [trace count]); + XCTAssertEqualObjects(trace[1][@"isPC"], nil); +#if TARGET_CPU_ARM || TARGET_CPU_ARM64 + XCTAssertEqualObjects(trace[1][@"isLR"], @YES); +#else + XCTAssertEqualObjects(trace[1][@"isLR"], nil); +#endif - XCTAssertEqual(1, [trace[0] count]); - XCTAssertTrue(trace[0][@"isPC"]); - XCTAssertEqual(0, [trace[1] count]); - XCTAssertEqual(0, [trace[2] count]); -} - -/** - * Frames not enhanced with lc/pr info in stack overflow - */ -- (void)testThreadEnhancementStackoverflow { - NSDictionary *dict = @{ - @"backtrace": @{ - @"contents": @[ - @{}, - @{}, - @{} - ] - }, - @"crashed": @YES, - @"stack": @{ - @"overflow": @YES - } - }; - NSDictionary *thread = [BugsnagThread enhanceThreadInfo:dict depth:0 errorType:@"signal"]; - XCTAssertNotEqual(dict, thread); - NSArray *trace = thread[@"backtrace"][@"contents"]; - XCTAssertEqual(3, [trace count]); - XCTAssertEqual(0, [trace[0] count]); - XCTAssertEqual(0, [trace[1] count]); - XCTAssertEqual(0, [trace[2] count]); + XCTAssertEqualObjects(trace[2][@"isPC"], nil); + XCTAssertEqualObjects(trace[2][@"isLR"], nil); } - (void)testStacktraceOverride { diff --git a/features/barebone_tests.feature b/features/barebone_tests.feature index d041456e1..8ed471a0f 100644 --- a/features/barebone_tests.feature +++ b/features/barebone_tests.feature @@ -113,6 +113,8 @@ Feature: Barebone tests And the error payload field "events.0.device.model" matches the test device model And the error payload field "events.0.device.totalMemory" is an integer And the error payload field "events.0.threads" is an array with 0 elements + And the "isPC" of stack frame 0 is null + And the "isLR" of stack frame 0 is null And the "method" of stack frame 0 matches "BareboneTestHandledScenario" And the stacktrace is valid for the event @@ -143,6 +145,8 @@ Feature: Barebone tests And the exception "errorClass" matches "SwiftNativeNSError" And the exception "message" equals "The data couldn’t be read because it isn’t in the correct format." And the exception "type" equals "cocoa" + And the "isPC" of stack frame 0 is null + And the "isLR" of stack frame 0 is null And the "method" of stack frame 0 matches "BareboneTestHandledScenario" And the stacktrace is valid for the event @@ -235,6 +239,8 @@ Feature: Barebone tests And on !watchOS, the error payload field "events.0.threads" is a non-empty array And on !watchOS, the error payload field "events.0.threads.1" is not null And the stacktrace is valid for the event + And the "isPC" of stack frame 0 is null + And the "isLR" of stack frame 0 is null @skip_macos Scenario: Barebone test: Out Of Memory diff --git a/features/crashprobe.feature b/features/crashprobe.feature index c73d305fd..0bc1b7a94 100644 --- a/features/crashprobe.feature +++ b/features/crashprobe.feature @@ -13,6 +13,10 @@ Feature: Reporting crash events | Intel | EXC_BAD_ACCESS | | ARM | EXC_BAD_INSTRUCTION | And the "method" of stack frame 0 equals "-[PrivilegedInstructionScenario run]" + And the "isPC" of stack frame 0 is true + And on x86, the "isLR" of stack frame 0 is null + And on x86, the "isLR" of stack frame 1 is null + And on x86, the "isPC" of stack frame 1 is null Scenario: Calling __builtin_trap() When I run "BuiltinTrapScenario" and relaunch the crashed app @@ -24,6 +28,10 @@ Feature: Reporting crash events | Intel | EXC_BAD_INSTRUCTION | | ARM | EXC_BREAKPOINT | And the "method" of stack frame 0 equals "-[BuiltinTrapScenario run]" + And the "isPC" of stack frame 0 is true + And on x86, the "isLR" of stack frame 0 is null + And on x86, the "isPC" of stack frame 1 is null + And on x86, the "isLR" of stack frame 1 is null Scenario: Calling non-existent method When I run "NonExistentMethodScenario" and relaunch the crashed app @@ -47,6 +55,8 @@ Feature: Reporting crash events | ___forwarding___ | And the "method" of stack frame 4 equals "_CF_forwarding_prep_0" And the "method" of stack frame 5 equals "-[NonExistentMethodScenario run]" + And the "isPC" of stack frame 0 is null + And the "isLR" of stack frame 0 is null Scenario: Trigger a crash after overwriting the link register When I run "OverwriteLinkRegisterScenario" and relaunch the crashed app @@ -56,6 +66,11 @@ Feature: Reporting crash events And the exception "errorClass" equals "EXC_BAD_ACCESS" And the exception "message" equals "Attempted to dereference null pointer." And the "method" of stack frame 0 equals "-[OverwriteLinkRegisterScenario run]" + And the "isPC" of stack frame 0 is true + And the "isLR" of stack frame 0 is null + And the "isPC" of stack frame 1 is null + And on arm, the "isLR" of stack frame 1 is true + And on x86, the "isLR" of stack frame 1 is null Scenario: Attempt to write into a read-only page When I run "ReadOnlyPageScenario" and relaunch the crashed app @@ -64,6 +79,11 @@ Feature: Reporting crash events Then the error is valid for the error reporting API And the exception "errorClass" equals "EXC_BAD_ACCESS" And the "method" of stack frame 0 equals "-[ReadOnlyPageScenario run]" + And the "isPC" of stack frame 0 is true + And the "isLR" of stack frame 0 is null + And the "isPC" of stack frame 1 is null + And on arm, the "isLR" of stack frame 1 is true + And on x86, the "isLR" of stack frame 1 is null Scenario: Stack overflow When I run "StackOverflowScenario" and relaunch the crashed app @@ -93,6 +113,11 @@ Feature: Reporting crash events | ARM | Attempted to dereference garbage pointer 0x38. | | Intel | Attempted to dereference garbage pointer 0x40. | And the "method" of stack frame 0 equals "objc_msgSend" + And the "isPC" of stack frame 0 is true + And the "isLR" of stack frame 0 is null + And the "isPC" of stack frame 1 is null + And on arm, the "isLR" of stack frame 1 is true + And on x86, the "isLR" of stack frame 1 is null Scenario: Attempt to execute an instruction undefined on the current architecture When I run "UndefinedInstructionScenario" and relaunch the crashed app @@ -101,6 +126,10 @@ Feature: Reporting crash events Then the error is valid for the error reporting API And the exception "errorClass" equals "EXC_BAD_INSTRUCTION" And the "method" of stack frame 0 equals "-[UndefinedInstructionScenario run]" + And the "isPC" of stack frame 0 is true + And on x86, the "isLR" of stack frame 0 is null + And on x86, the "isPC" of stack frame 1 is null + And on x86, the "isLR" of stack frame 1 is null Scenario: Send a message to an object whose memory has already been freed When I run "ReleasedObjectScenario" and relaunch the crashed app @@ -113,6 +142,11 @@ Feature: Reporting crash events And the "method" of stack frame 1 equals one of: | ARM | __29-[ReleasedObjectScenario run]_block_invoke | | Intel | -[ReleasedObjectScenario run] | + And the "isPC" of stack frame 0 is true + And the "isLR" of stack frame 0 is null + And the "isPC" of stack frame 1 is null + And on arm, the "isLR" of stack frame 1 is true + And on x86, the "isLR" of stack frame 1 is null Scenario: Crash within Swift code When I run "SwiftCrashScenario" and relaunch the crashed app @@ -122,6 +156,10 @@ Feature: Reporting crash events And the exception "errorClass" equals "Fatal error" And the exception "message" equals "Unexpectedly found nil while unwrapping an Optional value" And the event "metaData.error.crashInfo" matches "Fatal error: Unexpectedly found nil while unwrapping an Optional value" + And the "isPC" of stack frame 0 is true + And on x86, the "isLR" of stack frame 0 is null + And on x86, the "isPC" of stack frame 1 is null + And on x86, the "isLR" of stack frame 1 is null Scenario: Assertion failure in Swift code When I run "SwiftAssertionScenario" and relaunch the crashed app @@ -131,6 +169,10 @@ Feature: Reporting crash events And the exception "errorClass" equals "Fatal error" And the exception "message" equals "several unfortunate things just happened" And the event "metaData.error.crashInfo" matches "Fatal error: several unfortunate things just happened" + And the "isPC" of stack frame 0 is true + And on x86, the "isLR" of stack frame 0 is null + And on x86, the "isPC" of stack frame 1 is null + And on x86, the "isLR" of stack frame 1 is null Scenario: Dereference a null pointer When I run "NullPointerScenario" and relaunch the crashed app @@ -140,6 +182,11 @@ Feature: Reporting crash events And the exception "message" equals "Attempted to dereference null pointer." And the exception "errorClass" equals "EXC_BAD_ACCESS" And the "method" of stack frame 0 equals "-[NullPointerScenario run]" + And the "isPC" of stack frame 0 is true + And the "isLR" of stack frame 0 is null + And the "isPC" of stack frame 1 is null + And on arm, the "isLR" of stack frame 1 is true + And on x86, the "isLR" of stack frame 1 is null Scenario: Trigger a crash with libsystem_pthread's _pthread_list_lock held When I run "AsyncSafeThreadScenario" @@ -158,12 +205,22 @@ Feature: Reporting crash events And the stacktrace contains methods: # |pthread_getname_np| | -[AsyncSafeThreadScenario run] | + And the "isPC" of stack frame 0 is true + And the "isLR" of stack frame 0 is null + And the "isPC" of stack frame 1 is null + And on arm, the "isLR" of stack frame 1 is true + And on x86, the "isLR" of stack frame 1 is null Scenario: Trigger a crash with simulated malloc() lock held When I run "AsyncSafeMallocScenario" and relaunch the crashed app And I configure Bugsnag for "AsyncSafeMallocScenario" And I wait to receive an error And the exception "errorClass" equals "SIGABRT" + And the "isPC" of stack frame 0 is true + And the "isLR" of stack frame 0 is null + And the "isPC" of stack frame 1 is null + And on arm, the "isLR" of stack frame 1 is true + And on x86, the "isLR" of stack frame 1 is null Scenario: Read a garbage pointer When I run "ReadGarbagePointerScenario" and relaunch the crashed app @@ -173,6 +230,11 @@ Feature: Reporting crash events And the exception "message" starts with "Attempted to dereference garbage pointer" And the exception "errorClass" equals "EXC_BAD_ACCESS" And the "method" of stack frame 0 equals "-[ReadGarbagePointerScenario run]" + And the "isPC" of stack frame 0 is true + And the "isLR" of stack frame 0 is null + And the "isPC" of stack frame 1 is null + And on arm, the "isLR" of stack frame 1 is true + And on x86, the "isLR" of stack frame 1 is null Scenario: Access a non-object as an object When I run "AccessNonObjectScenario" and relaunch the crashed app @@ -182,6 +244,11 @@ Feature: Reporting crash events And the exception "message" equals "Attempted to dereference garbage pointer 0x10." And the exception "errorClass" equals "EXC_BAD_ACCESS" And the "method" of stack frame 0 equals "objc_msgSend" + And the "isPC" of stack frame 0 is true + And the "isLR" of stack frame 0 is null + And the "isPC" of stack frame 1 is null + And on arm, the "isLR" of stack frame 1 is true + And on x86, the "isLR" of stack frame 1 is null Scenario: Misuse of libdispatch When I run "DispatchCrashScenario" and relaunch the crashed app @@ -193,6 +260,9 @@ Feature: Reporting crash events | Intel | EXC_BAD_INSTRUCTION | And the exception "message" starts with "BUG IN CLIENT OF LIBDISPATCH: dispatch_" And the event "metaData.error.crashInfo" starts with "BUG IN CLIENT OF LIBDISPATCH: dispatch_" + And the "isPC" of stack frame 0 is true + And the "isLR" of stack frame 0 is null + And the "isPC" of stack frame 1 is null Scenario: Concurrent crashes should result in a single valid crash report Given I run "ConcurrentCrashesScenario" and relaunch the crashed app diff --git a/features/steps/cocoa_steps.rb b/features/steps/cocoa_steps.rb index 3a40127d9..6addb17f8 100644 --- a/features/steps/cocoa_steps.rb +++ b/features/steps/cocoa_steps.rb @@ -2,6 +2,11 @@ sleep 2 end +Then(/^on (arm|x86), (.+)/) do |step_arch, step_text| + binary_arch = Maze::Helper.read_key_path(Maze::Server.errors.current[:body], 'events.0.app.binaryArch') + step(step_text) if binary_arch.start_with? step_arch +end + Then('the error payload field {string} is equal for error {int} and error {int}') do |key, index_a, index_b| Maze.check.true(request_fields_are_equal(key, index_a, index_b)) end