| // |
| // 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 |