Skip to content

Commit

Permalink
Merge branch 'jmarino_fix_symbolicator'
Browse files Browse the repository at this point in the history
  • Loading branch information
Adam Sharp committed Apr 30, 2015
2 parents d8ead96 + 009e3ba commit 32b08d7
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 65 deletions.
38 changes: 38 additions & 0 deletions Classes/Core/KWBackgroundTask.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// Licensed under the terms in License.txt
//
// Copyright 2010 Allen Ding. All rights reserved.
//

#import <Foundation/Foundation.h>

// Used to suppress compiler warnings by
// casting receivers to this protocol
@protocol NSTask_KWWarningSuppressor

- (void)setLaunchPath:(NSString *)path;
- (void)setArguments:(NSArray *)arguments;
- (void)setEnvironment:(NSDictionary *)dict;
- (void)setStandardOutput:(id)output;
- (void)setStandardError:(id)output;
- (void)launch;
- (void)waitUntilExit;

@property (readonly) int terminationStatus;

@end

@interface KWBackgroundTask : NSObject

@property (nonatomic, readonly) id<NSTask_KWWarningSuppressor> task;
@property (nonatomic, readonly) NSPipe *standardOutput;
@property (nonatomic, readonly) NSPipe *standardError;
@property (nonatomic, readonly) NSString *command;
@property (nonatomic, readonly) NSArray *arguments;
@property (nonatomic, readonly) NSData *output;

- (instancetype)initWithCommand:(NSString *)command arguments:(NSArray *)arguments;

- (void)launchAndWaitForExit;

@end
95 changes: 95 additions & 0 deletions Classes/Core/KWBackgroundTask.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//
// Licensed under the terms in License.txt
//
// Copyright 2010 Allen Ding. All rights reserved.
//

#import "KWBackgroundTask.h"

NSString *const NSTaskDidTerminateNotification;

static NSString *const KWTaskDidTerminateNotification = @"KWTaskDidTerminateNotification";

static NSString *const KWBackgroundTaskException = @"KWBackgroundTaskException";

@implementation KWBackgroundTask

- (instancetype)initWithCommand:(NSString *)command arguments:(NSArray *)arguments {
if (self = [super init]) {
_command = command;
_arguments = arguments;
}
return self;
}

- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSTaskDidTerminateNotification object:nil];
}

- (NSString *)description {
return [NSString stringWithFormat:@"%@ `%@ %@`", [super description], self.command, [self.arguments componentsJoinedByString:@" "]];
}

// Run this task for 10 seconds
// if it times out raise an exception
- (void)launchAndWaitForExit {
CFRunLoopRef runLoop = [NSRunLoop currentRunLoop].getCFRunLoop;
__weak KWBackgroundTask *weakSelf = self;
CFRunLoopTimerRef timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + 10.0, 0, 0, 0, ^(CFRunLoopTimerRef timer) {
[NSException raise:KWBackgroundTaskException format:@"Task %@ timed out", weakSelf];
CFRunLoopStop(runLoop);
});
CFRunLoopAddTimer(runLoop, timer, kCFRunLoopDefaultMode);

id taskObserver = [[NSNotificationCenter defaultCenter] addObserverForName:KWTaskDidTerminateNotification object:self queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
CFRunLoopStop(runLoop);
}];

[NSThread detachNewThreadSelector:@selector(launch) toTarget:self withObject:nil];
CFRunLoopRun();
CFRunLoopRemoveTimer(runLoop, timer, kCFRunLoopDefaultMode);

[[NSNotificationCenter defaultCenter] removeObserver:taskObserver];
}

#pragma mark - Private

- (void)launch {
__block id<NSTask_KWWarningSuppressor> task = [[NSClassFromString(@"NSTask") alloc] init];
[task setEnvironment:[NSDictionary dictionary]];
[task setLaunchPath:_command];
[task setArguments:_arguments];

NSPipe *standardOutput = [NSPipe pipe];
[task setStandardOutput:standardOutput];

// Consume standard error but don't use it
NSPipe *standardError = [NSPipe pipe];
[task setStandardError:standardError];

_task = task;
_standardError = standardError;
_standardOutput = standardOutput;

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidTerminate:) name:NSTaskDidTerminateNotification object:task];

@try {
[_task launch];
} @catch (NSException *exception) {
[NSException raise:KWBackgroundTaskException format:@"Task %@ failed to launch", self];
}
CFRunLoopRun();
}

- (void)taskDidTerminate:(NSNotification *)note {
if ([_task terminationStatus] != 0) {
[NSException raise:KWBackgroundTaskException format:@"Task %@ terminated with non 0 exit code", self];
} else {
_output = [[_standardOutput fileHandleForReading] readDataToEndOfFile];
}

[[NSNotificationCenter defaultCenter] postNotificationName:KWTaskDidTerminateNotification object:self];
[NSThread exit];
}

@end
22 changes: 1 addition & 21 deletions Classes/Core/KWExample.m
Original file line number Diff line number Diff line change
Expand Up @@ -308,27 +308,7 @@ - (NSString *)generateDescriptionForAnonymousItNode {

KWCallSite *callSiteAtAddressIfNecessary(long address){
BOOL shouldLookup = [[KWExampleSuiteBuilder sharedExampleSuiteBuilder] isFocused] && ![[KWExampleSuiteBuilder sharedExampleSuiteBuilder] foundFocus];
return shouldLookup ? callSiteWithAddress(address) : nil;
}

KWCallSite *callSiteWithAddress(long address){
NSArray *args = @[@"-p", @(getpid()).stringValue, [NSString stringWithFormat:@"%lx", address]];
NSString *callSite = [NSString stringWithShellCommand:@"/usr/bin/atos" arguments:args];

NSString *pattern = @".+\\((.+):([0-9]+)\\)";
NSError *e;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:&e];
NSArray *res = [regex matchesInString:callSite options:0 range:NSMakeRange(0, callSite.length)];

NSString *fileName = nil;
NSInteger lineNumber = 0;

for (NSTextCheckingResult *ntcr in res) {
fileName = [callSite substringWithRange:[ntcr rangeAtIndex:1]];
NSString *lineNumberMatch = [callSite substringWithRange:[ntcr rangeAtIndex:2]];
lineNumber = lineNumberMatch.integerValue;
}
return [KWCallSite callSiteWithFilename:fileName lineNumber:lineNumber];
return shouldLookup ? [KWCallSite callSiteWithCallerAddress:address] : nil;
}

#pragma mark - Building Example Groups
Expand Down
6 changes: 4 additions & 2 deletions Classes/Core/KWSymbolicator.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
//

#import <Foundation/Foundation.h>
#import "KWSymbolicator.h"
#import "KWCallSite.h"

long kwCallerAddress(void);

@interface NSString (KWShellCommand)
@interface KWCallSite (KWSymbolication)

+ (NSString *)stringWithShellCommand:(NSString *)command arguments:(NSArray *)arguments;
+ (KWCallSite *)callSiteWithCallerAddress:(long)address;

@end
64 changes: 40 additions & 24 deletions Classes/Core/KWSymbolicator.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
// Copyright (c) 2013 Allen Ding. All rights reserved.
//

#import "KWSymbolicator.h"
#import <objc/runtime.h>
#import <libunwind.h>
#import <pthread.h>
#import <mach-o/dyld.h>
#import "KWSymbolicator.h"
#import "KWBackgroundTask.h"

long kwCallerAddress (void){
#if !__arm__
Expand All @@ -27,36 +30,49 @@ long kwCallerAddress (void){
return 0;
}

// Used to suppress compiler warnings by
// casting receivers to this protocol
@protocol NSTask_KWWarningSuppressor
@implementation KWCallSite (KWSymbolication)

- (void)setLaunchPath:(NSString *)path;
- (void)setArguments:(NSArray *)arguments;
- (void)setEnvironment:(NSDictionary *)dict;
- (void)setStandardOutput:(id)output;
- (void)launch;
- (void)waitUntilExit;
static void GetTestBundleExecutablePathSlide(NSString **executablePath, long *slide) {
for (int i = 0; i < _dyld_image_count(); i++) {
if (strstr(_dyld_get_image_name(i), ".octest/") || strstr(_dyld_get_image_name(i), ".xctest/")) {
*executablePath = [NSString stringWithUTF8String:_dyld_get_image_name(i)];
*slide = _dyld_get_image_vmaddr_slide(i);
break;
}
}
}

@end
+ (KWCallSite *)callSiteWithCallerAddress:(long)address {
// Symbolicate the address with atos to get the line number & filename.
// If the command raises, no specs will run so don't bother catching
// In the case of a non 0 exit code, failure to launch, or timeout, the
// user will atleast have an idea of why the task failed.

long slide;
NSString *executablePath;
GetTestBundleExecutablePathSlide(&executablePath, &slide);
NSArray *arguments = @[@"-o", executablePath, @"-s", [NSString stringWithFormat:@"%lx", slide], [NSString stringWithFormat:@"%lx", address]];

@implementation NSString (KWShellCommand)
// See atos man page for more information on arguments.
KWBackgroundTask *symbolicationTask = [[KWBackgroundTask alloc] initWithCommand:@"/usr/bin/atos" arguments:arguments];
[symbolicationTask launchAndWaitForExit];

+ (NSString *)stringWithShellCommand:(NSString *)command arguments:(NSArray *)arguments {
id<NSTask_KWWarningSuppressor> task = [[NSClassFromString(@"NSTask") alloc] init];
[task setEnvironment:[NSDictionary dictionary]];
[task setLaunchPath:command];
[task setArguments:arguments];
NSString *symbolicatedCallerAddress = [[NSString alloc] initWithData:symbolicationTask.output encoding:NSUTF8StringEncoding];

NSPipe *pipe = [NSPipe pipe];
[task setStandardOutput:pipe];
[task launch];
NSString *pattern = @".+\\((.+):([0-9]+)\\)";
NSError *error;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:&error];
NSArray *matches = [regex matchesInString:symbolicatedCallerAddress options:0 range:NSMakeRange(0, symbolicatedCallerAddress.length)];

[task waitUntilExit];
NSString *fileName;
NSInteger lineNumber = 0;

NSData *data = [[pipe fileHandleForReading] readDataToEndOfFile];
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
return string;
for (NSTextCheckingResult *ntcr in matches) {
fileName = [symbolicatedCallerAddress substringWithRange:[ntcr rangeAtIndex:1]];
NSString *lineNumberMatch = [symbolicatedCallerAddress substringWithRange:[ntcr rangeAtIndex:2]];
lineNumber = lineNumberMatch.integerValue;
}
return [KWCallSite callSiteWithFilename:fileName lineNumber:lineNumber];
}

@end
Loading

0 comments on commit 32b08d7

Please sign in to comment.