blob: c2724d4ba1cfc6a62243d640a799763d1d76acb9 [file] [log] [blame] [edit]
//
// RACBacktrace.m
// ReactiveCocoa
//
// Created by Justin Spahr-Summers on 2012-08-16.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import <execinfo.h>
#import <pthread.h>
#import "RACBacktrace.h"
#define RAC_BACKTRACE_MAX_CALL_STACK_FRAMES 128
#ifdef DEBUG
// Undefine the macros that hide the real GCD functions.
#undef dispatch_async
#undef dispatch_barrier_async
#undef dispatch_after
#undef dispatch_async_f
#undef dispatch_barrier_async_f
#undef dispatch_after_f
@interface RACBacktrace () {
void *_callStackAddresses[RAC_BACKTRACE_MAX_CALL_STACK_FRAMES];
int _callStackSize;
}
@property (nonatomic, strong, readwrite) RACBacktrace *previousThreadBacktrace;
@end
@interface RACDispatchInfo : NSObject
// The recorded backtrace.
@property (nonatomic, strong, readonly) RACBacktrace *backtrace;
// The information for the original dispatch.
@property (nonatomic, readonly) dispatch_function_t function;
@property (nonatomic, readonly) void *context;
@property (nonatomic, readonly) dispatch_queue_t queue;
- (id)initWithQueue:(dispatch_queue_t)queue function:(dispatch_function_t)function context:(void *)context;
@end
// Function for use with dispatch_async_f and friends, which will save the
// backtrace onto the current queue, then call through to the original dispatch.
static void RACTraceDispatch (void *ptr) {
// Balance out the retain necessary for async calls.
RACDispatchInfo *info __attribute__((objc_precise_lifetime)) = CFBridgingRelease(ptr);
dispatch_queue_set_specific(info.queue, (void *)pthread_self(), (__bridge void *)info.backtrace, NULL);
info.function(info.context);
dispatch_queue_set_specific(info.queue, (void *)pthread_self(), NULL, NULL);
}
// Always inline this function, for consistency in backtraces.
__attribute__((always_inline))
static dispatch_block_t RACBacktraceBlock (dispatch_queue_t queue, dispatch_block_t block) {
RACBacktrace *backtrace = [RACBacktrace backtrace];
return [^{
RACBacktrace *backtraceKeptAlive __attribute__((objc_precise_lifetime)) = backtrace;
dispatch_queue_set_specific(queue, (void *)pthread_self(), (__bridge void *)backtraceKeptAlive, NULL);
block();
dispatch_queue_set_specific(queue, (void *)pthread_self(), NULL, NULL);
} copy];
}
void rac_dispatch_async(dispatch_queue_t queue, dispatch_block_t block) {
dispatch_async(queue, RACBacktraceBlock(queue, block));
}
void rac_dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block) {
dispatch_barrier_async(queue, RACBacktraceBlock(queue, block));
}
void rac_dispatch_after(dispatch_time_t time, dispatch_queue_t queue, dispatch_block_t block) {
dispatch_after(time, queue, RACBacktraceBlock(queue, block));
}
void rac_dispatch_async_f(dispatch_queue_t queue, void *context, dispatch_function_t function) {
RACDispatchInfo *info = [[RACDispatchInfo alloc] initWithQueue:queue function:function context:context];
dispatch_async_f(queue, (void *)CFBridgingRetain(info), &RACTraceDispatch);
}
void rac_dispatch_barrier_async_f(dispatch_queue_t queue, void *context, dispatch_function_t function) {
RACDispatchInfo *info = [[RACDispatchInfo alloc] initWithQueue:queue function:function context:context];
dispatch_barrier_async_f(queue, (void *)CFBridgingRetain(info), &RACTraceDispatch);
}
void rac_dispatch_after_f(dispatch_time_t time, dispatch_queue_t queue, void *context, dispatch_function_t function) {
RACDispatchInfo *info = [[RACDispatchInfo alloc] initWithQueue:queue function:function context:context];
dispatch_after_f(time, queue, (void *)CFBridgingRetain(info), &RACTraceDispatch);
}
// This is what actually performs the injection.
//
// The DYLD_INSERT_LIBRARIES environment variable must include the RAC dynamic
// library in order for this to work.
__attribute__((used)) static struct { const void *replacement; const void *replacee; } interposers[] __attribute__((section("__DATA,__interpose"))) = {
{ (const void *)&rac_dispatch_async, (const void *)&dispatch_async },
{ (const void *)&rac_dispatch_barrier_async, (const void *)&dispatch_barrier_async },
{ (const void *)&rac_dispatch_after, (const void *)&dispatch_after },
{ (const void *)&rac_dispatch_async_f, (const void *)&dispatch_async_f },
{ (const void *)&rac_dispatch_barrier_async_f, (const void *)&dispatch_barrier_async_f },
{ (const void *)&rac_dispatch_after_f, (const void *)&dispatch_after_f },
};
static void RACSignalHandler (int sig) {
NSLog(@"Backtrace: %@", [RACBacktrace backtrace]);
fflush(stdout);
// Restore the default action and raise the signal again.
signal(sig, SIG_DFL);
raise(sig);
}
static void RACExceptionHandler (NSException *ex) {
NSLog(@"Uncaught exception %@", ex);
NSLog(@"Backtrace: %@", [RACBacktrace backtrace]);
fflush(stdout);
}
@implementation RACBacktrace
#pragma mark Properties
- (NSArray *)callStackSymbols {
if (_callStackSize == 0) return @[];
char **symbols = backtrace_symbols(_callStackAddresses, _callStackSize);
NSMutableArray *array = [NSMutableArray arrayWithCapacity:(NSUInteger)_callStackSize];
for (int i = 0; i < _callStackSize; i++) {
NSString *str = @(symbols[i]);
[array addObject:str];
}
free(symbols);
return array;
}
#pragma mark Lifecycle
+ (void)load {
@autoreleasepool {
NSString *libraries = [[[NSProcessInfo processInfo] environment] objectForKey:@"DYLD_INSERT_LIBRARIES"];
// Don't install our handlers if we're not actually intercepting function
// calls.
if ([libraries rangeOfString:@"ReactiveCocoa"].length == 0) return;
NSLog(@"*** Enabling asynchronous backtraces");
NSSetUncaughtExceptionHandler(&RACExceptionHandler);
}
signal(SIGILL, &RACSignalHandler);
signal(SIGTRAP, &RACSignalHandler);
signal(SIGABRT, &RACSignalHandler);
signal(SIGFPE, &RACSignalHandler);
signal(SIGBUS, &RACSignalHandler);
signal(SIGSEGV, &RACSignalHandler);
signal(SIGSYS, &RACSignalHandler);
signal(SIGPIPE, &RACSignalHandler);
}
- (void)dealloc {
__autoreleasing RACBacktrace *previous __attribute__((unused)) = self.previousThreadBacktrace;
self.previousThreadBacktrace = nil;
}
#pragma mark Backtraces
+ (instancetype)backtrace {
return [self backtraceIgnoringFrames:1];
}
+ (instancetype)backtraceIgnoringFrames:(NSUInteger)ignoreCount {
@autoreleasepool {
RACBacktrace *oldBacktrace = (__bridge id)dispatch_get_specific((void *)pthread_self());
RACBacktrace *newBacktrace = [[RACBacktrace alloc] init];
newBacktrace.previousThreadBacktrace = oldBacktrace;
int size = backtrace(newBacktrace->_callStackAddresses, RAC_BACKTRACE_MAX_CALL_STACK_FRAMES);
// Omit this method plus however many others from the backtrace.
++ignoreCount;
if ((NSUInteger)size > ignoreCount) {
memmove(newBacktrace->_callStackAddresses, newBacktrace->_callStackAddresses + ignoreCount, ((NSUInteger)size - ignoreCount) * sizeof(char *));
size -= (int)ignoreCount;
}
newBacktrace->_callStackSize = size;
return newBacktrace;
}
}
#pragma mark NSObject
- (NSString *)description {
NSString *str = [NSString stringWithFormat:@"%@", self.callStackSymbols];
if (self.previousThreadBacktrace != nil) {
str = [str stringByAppendingFormat:@"\n\n... asynchronously invoked from: %@", self.previousThreadBacktrace];
}
return str;
}
@end
@implementation RACDispatchInfo
#pragma mark Lifecycle
- (id)initWithQueue:(dispatch_queue_t)queue function:(dispatch_function_t)function context:(void *)context {
@autoreleasepool {
NSCParameterAssert(queue != NULL);
NSCParameterAssert(function != NULL);
self = [super init];
if (self == nil) return nil;
_backtrace = [RACBacktrace backtraceIgnoringFrames:1];
dispatch_retain(queue);
_queue = queue;
_function = function;
_context = context;
return self;
}
}
- (void)dealloc {
if (_queue != NULL) {
dispatch_release(_queue);
_queue = NULL;
}
}
@end
#endif