From 5894ad38349941fec0a2835fd456ad98a25d5803 Mon Sep 17 00:00:00 2001 From: Pritesh Nandgaonkar Date: Fri, 7 Dec 2018 07:29:01 -0800 Subject: [PATCH] Catch signal errors Summary: This PR adds support to catch and report signal errors to the flipper. It also reports the callstack. Please look at the video in the test plan to understand the what it does. Reviewed By: jknoxville Differential Revision: D13328818 fbshipit-source-id: 304a68f47bb5ca5b0014c8b7d30f6a6fc2b6d147 --- .../FlipperKitCrashReporterPlugin.h | 16 +- .../FlipperKitCrashReporterPlugin.mm | 15 +- .../FlipperKitSignalHandler.h | 143 ++++++++++++++++++ 3 files changed, 166 insertions(+), 8 deletions(-) create mode 100644 iOS/Plugins/FlipperKitCrashReporterPlugin/FlipperKitCrashReporterPlugin/FlipperKitSignalHandler.h diff --git a/iOS/Plugins/FlipperKitCrashReporterPlugin/FlipperKitCrashReporterPlugin/FlipperKitCrashReporterPlugin.h b/iOS/Plugins/FlipperKitCrashReporterPlugin/FlipperKitCrashReporterPlugin/FlipperKitCrashReporterPlugin.h index 0eb964c20..ef040061b 100644 --- a/iOS/Plugins/FlipperKitCrashReporterPlugin/FlipperKitCrashReporterPlugin/FlipperKitCrashReporterPlugin.h +++ b/iOS/Plugins/FlipperKitCrashReporterPlugin/FlipperKitCrashReporterPlugin/FlipperKitCrashReporterPlugin.h @@ -1,19 +1,21 @@ -/* - * Copyright (c) Facebook, Inc. - * - * This source code is licensed under the MIT license found in the LICENSE - * file in the root directory of this source tree. +/** + * Copyright (c) Facebook, Inc. and its affiliates. * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. */ #if FB_SONARKIT_ENABLED #import #import -@interface FlipperKitCrashReporterPlugin : NSObject +@protocol CrashReporterDelegate +- (void)sendCrashParams:(NSDictionary *)params; +@end + +@interface FlipperKitCrashReporterPlugin : NSObject - (instancetype)init NS_UNAVAILABLE; + (instancetype) sharedInstance; @end - #endif diff --git a/iOS/Plugins/FlipperKitCrashReporterPlugin/FlipperKitCrashReporterPlugin/FlipperKitCrashReporterPlugin.mm b/iOS/Plugins/FlipperKitCrashReporterPlugin/FlipperKitCrashReporterPlugin/FlipperKitCrashReporterPlugin.mm index e80e19730..eba09b397 100644 --- a/iOS/Plugins/FlipperKitCrashReporterPlugin/FlipperKitCrashReporterPlugin/FlipperKitCrashReporterPlugin.mm +++ b/iOS/Plugins/FlipperKitCrashReporterPlugin/FlipperKitCrashReporterPlugin/FlipperKitCrashReporterPlugin.mm @@ -8,11 +8,14 @@ #if FB_SONARKIT_ENABLED #import "FlipperKitCrashReporterPlugin.h" #import +#include +#import "FlipperKitSignalHandler.h" @interface FlipperKitCrashReporterPlugin() @property (strong, nonatomic) id connection; @property (assign, nonatomic) NSUInteger notificationID; @property (assign, nonatomic) NSUncaughtExceptionHandler *prevHandler; + - (void) handleException:(NSException *)exception; @end @@ -23,7 +26,10 @@ static void flipperkitUncaughtExceptionHandler(NSException *exception) { [[FlipperKitCrashReporterPlugin sharedInstance] handleException:exception]; } -@implementation FlipperKitCrashReporterPlugin +@implementation FlipperKitCrashReporterPlugin { + std::unique_ptr _signalHandler; + folly::ScopedEventBaseThread _crashReporterThread; +} - (instancetype)init { if (self = [super init]) { @@ -58,13 +64,20 @@ static void flipperkitUncaughtExceptionHandler(NSException *exception) { } } +- (void)sendCrashParams:(NSDictionary *)params { + self.notificationID += 1; + [self.connection send:@"crash-report" withParams: params]; +} + - (void)didConnect:(id)connection { self.connection = connection; + _signalHandler = std::make_unique(self, _crashReporterThread.getEventBase()); NSSetUncaughtExceptionHandler(&flipperkitUncaughtExceptionHandler); } - (void)didDisconnect { self.connection = nil; + _signalHandler.reset(nullptr); // deallocate the object NSSetUncaughtExceptionHandler(self.prevHandler); } diff --git a/iOS/Plugins/FlipperKitCrashReporterPlugin/FlipperKitCrashReporterPlugin/FlipperKitSignalHandler.h b/iOS/Plugins/FlipperKitCrashReporterPlugin/FlipperKitCrashReporterPlugin/FlipperKitSignalHandler.h new file mode 100644 index 000000000..e44d4c9a6 --- /dev/null +++ b/iOS/Plugins/FlipperKitCrashReporterPlugin/FlipperKitCrashReporterPlugin/FlipperKitSignalHandler.h @@ -0,0 +1,143 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + */ +#ifndef __OBJC__ +#error This header can only be included in .mm (ObjC++) files +#endif + +#if FB_SONARKIT_ENABLED + +#pragma once + +#include +#import "FlipperKitCrashReporterPlugin.h" +#import +#include +#include +#include +#include +#include + +namespace facebook { + namespace flipper { + using ObjCPlugin = NSObject *; + + class FlipperKitSignalHandler : public folly::AsyncSignalHandler { + + struct CrashDetails { + int signalType; + std::string reason; + std::vector callStack; + NSTimeInterval time; //In milliseconds + + bool operator==(const CrashDetails& rhs) + { + return signalType == rhs.signalType && reason == rhs.reason && callStack == rhs.callStack && (abs(rhs.time - time) < 10); + } + + bool operator!=(const CrashDetails& rhs) { + return !(*this == rhs); + } + + + }; + + public: + FlipperKitSignalHandler(ObjCPlugin reporterPlugin, folly::EventBase *eventBase): folly::AsyncSignalHandler(eventBase), plugin_(reporterPlugin), eventbase_(eventBase) { + + eventBase->add([this]{ + registerSignalHandler(SIGILL); + registerSignalHandler(SIGSEGV); + registerSignalHandler(SIGFPE); + registerSignalHandler(SIGBUS); + registerSignalHandler(SIGABRT); + }); + } + + void unregisterSignalHandler() { + folly::AsyncSignalHandler::unregisterSignalHandler(SIGILL); // illegal instruction + folly::AsyncSignalHandler::unregisterSignalHandler(SIGSEGV); // segmentation violation + folly::AsyncSignalHandler::unregisterSignalHandler(SIGFPE); // floating point exception + folly::AsyncSignalHandler::unregisterSignalHandler(SIGBUS); // Bus error + folly::AsyncSignalHandler::unregisterSignalHandler(SIGABRT); // Abort + } + + ~FlipperKitSignalHandler() { + unregisterSignalHandler(); + plugin_ = nullptr; + } + + private: + ObjCPlugin plugin_; + folly::EventBase *eventbase_; + std::mutex mtx; + CrashDetails lastCrashDetails_; + + std::string signalType(int signal) { + switch (signal) { + case SIGILL: + return "SIGILL"; + case SIGSEGV: + return "SIGSEGV"; + case SIGFPE: + return "SIGFPE"; + case SIGBUS: + return "SIGBUS"; + case SIGABRT: + return "SIGABRT"; + default: + return "Unregistered Signal"; + } + } + + std::string reasonForSignalError(int signal) { + switch (signal) { + case SIGILL: + return "Illegal Instruction"; + case SIGSEGV: + return "Segmentation Violation"; + case SIGFPE: + return "Floating Point Exception"; + case SIGBUS: + return "Bus Error"; + case SIGABRT: + return "Abort Signal"; + default: + return "Unregistered Signal"; + } + } + + void signalReceived(int signum) noexcept override { + void* callstack[2048]; + int frames = backtrace(callstack, 2048); + char **strs = backtrace_symbols(callstack, frames); + folly::dynamic arr = folly::dynamic::array(); + std::vector vec; + for (int i = 0; i < frames; ++i) { + NSString *str = [NSString stringWithUTF8String:strs[i]]; + vec.push_back(std::string([str UTF8String])); + arr.push_back(std::string([str UTF8String])); + } + std::string reasonforSignalError = reasonForSignalError(signum); + CrashDetails currentCrash({signum, reasonforSignalError, vec, [[NSDate date] timeIntervalSince1970] * 1000}); + // This check is added, because I reproduced a scenario where lot of signal errors of same kind were thrown in a short period of span. So this basically makes sure that we just send one notification. + // To reproduce that case, use this snippet `void (*nullFunction)() = NULL; nullFunction();`, it will cause segfault. + if (lastCrashDetails_ != currentCrash) { + [plugin_ sendCrashParams:facebook::cxxutils::convertFollyDynamicToId(folly::dynamic::object("reason", reasonforSignalError)("name", std::string("Signal Error ") + signalType(signum))("callstack", arr))]; + + eventbase_->runAfterDelay([this, signum]{ + unregisterSignalHandler(); // Unregister signal handler as we will be reraising the signal. + // Raising the signal after delay, because message is sent in an asyncronous way to flipper. If its raised with no delay then its observed that flipper doesn't receive the message + raise(signum);}, 10); + } + lastCrashDetails_ = currentCrash; + } + + }; + + } // namespace flipper +} // namespace facebook +#endif