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
This commit is contained in:
Pritesh Nandgaonkar
2018-12-07 07:29:01 -08:00
committed by Facebook Github Bot
parent 7eb1546f25
commit 5894ad3834
3 changed files with 166 additions and 8 deletions

View File

@@ -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 <Foundation/Foundation.h>
#import <FlipperKit/FlipperPlugin.h>
@interface FlipperKitCrashReporterPlugin : NSObject<FlipperPlugin>
@protocol CrashReporterDelegate
- (void)sendCrashParams:(NSDictionary *)params;
@end
@interface FlipperKitCrashReporterPlugin : NSObject<FlipperPlugin, CrashReporterDelegate>
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype) sharedInstance;
@end
#endif

View File

@@ -8,11 +8,14 @@
#if FB_SONARKIT_ENABLED
#import "FlipperKitCrashReporterPlugin.h"
#import <FlipperKit/FlipperConnection.h>
#include <folly/io/async/ScopedEventBaseThread.h>
#import "FlipperKitSignalHandler.h"
@interface FlipperKitCrashReporterPlugin()
@property (strong, nonatomic) id<FlipperConnection> 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<facebook::flipper::FlipperKitSignalHandler> _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<FlipperConnection>)connection {
self.connection = connection;
_signalHandler = std::make_unique<facebook::flipper::FlipperKitSignalHandler>(self, _crashReporterThread.getEventBase());
NSSetUncaughtExceptionHandler(&flipperkitUncaughtExceptionHandler);
}
- (void)didDisconnect {
self.connection = nil;
_signalHandler.reset(nullptr); // deallocate the object
NSSetUncaughtExceptionHandler(self.prevHandler);
}

View File

@@ -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 <folly/io/async/AsyncSignalHandler.h>
#import "FlipperKitCrashReporterPlugin.h"
#import <FBCxxUtils/FBCxxFollyDynamicConvert.h>
#include <folly/io/async/ScopedEventBaseThread.h>
#include <execinfo.h>
#include <iostream>
#include <mutex>
#include <chrono>
namespace facebook {
namespace flipper {
using ObjCPlugin = NSObject<CrashReporterDelegate> *;
class FlipperKitSignalHandler : public folly::AsyncSignalHandler {
struct CrashDetails {
int signalType;
std::string reason;
std::vector<std::string> 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<std::string> 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