// // GTMStackTrace.m // // Copyright 2007-2008 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy // of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. // #include #include #include #include "GTMStackTrace.h" #include "GTMObjC2Runtime.h" struct GTMClassDescription { const char *class_name; Method *class_methods; unsigned int class_method_count; Method *instance_methods; unsigned int instance_method_count; }; #pragma mark Private utility functions static struct GTMClassDescription *GTMClassDescriptions(NSUInteger *total_count) { int class_count = objc_getClassList(nil, 0); struct GTMClassDescription *class_descs = calloc(class_count, sizeof(struct GTMClassDescription)); if (class_descs) { Class *classes = calloc(class_count, sizeof(Class)); if (classes) { objc_getClassList(classes, class_count); for (int i = 0; i < class_count; ++i) { class_descs[i].class_methods = class_copyMethodList(object_getClass(classes[i]), &class_descs[i].class_method_count); class_descs[i].instance_methods = class_copyMethodList(classes[i], &class_descs[i].instance_method_count); class_descs[i].class_name = class_getName(classes[i]); } free(classes); } else { // COV_NF_START - Don't know how to force this in a unittest free(class_descs); class_descs = NULL; class_count = 0; // COV_NF_END } } if (total_count) { *total_count = class_count; } return class_descs; } static void GTMFreeClassDescriptions(struct GTMClassDescription *class_descs, NSUInteger count) { if (!class_descs) return; for (NSUInteger i = 0; i < count; ++i) { if (class_descs[i].instance_methods) { free(class_descs[i].instance_methods); } if (class_descs[i].class_methods) { free(class_descs[i].class_methods); } } free(class_descs); } static NSUInteger GTMGetStackAddressDescriptorsForAddresses(void *pcs[], struct GTMAddressDescriptor outDescs[], NSUInteger count) { if (count < 1 || !pcs || !outDescs) return 0; NSUInteger class_desc_count; // Get our obj-c class descriptions. This is expensive, so we do it once // at the top. We go through this because dladdr doesn't work with // obj methods. struct GTMClassDescription *class_descs = GTMClassDescriptions(&class_desc_count); if (class_descs == NULL) { class_desc_count = 0; } // Iterate through the stack. for (NSUInteger i = 0; i < count; ++i) { const char *class_name = NULL; BOOL is_class_method = NO; size_t smallest_diff = SIZE_MAX; struct GTMAddressDescriptor *currDesc = &outDescs[i]; currDesc->address = pcs[i]; Method best_method = NULL; // Iterate through all the classes we know of. for (NSUInteger j = 0; j < class_desc_count; ++j) { // First check the class methods. for (NSUInteger k = 0; k < class_descs[j].class_method_count; ++k) { void *imp = (void *)method_getImplementation(class_descs[j].class_methods[k]); if (imp <= currDesc->address) { size_t diff = (size_t)currDesc->address - (size_t)imp; if (diff < smallest_diff) { best_method = class_descs[j].class_methods[k]; class_name = class_descs[j].class_name; is_class_method = YES; smallest_diff = diff; } } } // Then check the instance methods. for (NSUInteger k = 0; k < class_descs[j].instance_method_count; ++k) { void *imp = (void *)method_getImplementation(class_descs[j].instance_methods[k]); if (imp <= currDesc->address) { size_t diff = (size_t)currDesc->address - (size_t)imp; if (diff < smallest_diff) { best_method = class_descs[j].instance_methods[k]; class_name = class_descs[j].class_name; is_class_method = NO; smallest_diff = diff; } } } } // If we have one, store it off. if (best_method) { currDesc->symbol = sel_getName(method_getName(best_method)); currDesc->is_class_method = is_class_method; currDesc->class_name = class_name; } Dl_info info = { NULL, NULL, NULL, NULL }; // Check to see if the one returned by dladdr is better. dladdr(currDesc->address, &info); if ((size_t)currDesc->address - (size_t)info.dli_saddr < smallest_diff) { currDesc->symbol = info.dli_sname; currDesc->is_class_method = NO; currDesc->class_name = NULL; } currDesc->filename = info.dli_fname; if (!currDesc->symbol) { currDesc->symbol = "???"; currDesc->is_class_method = NO; currDesc->class_name = NULL; } } GTMFreeClassDescriptions(class_descs, class_desc_count); return count; } static NSString *GTMStackTraceFromAddressDescriptors(struct GTMAddressDescriptor descs[], NSUInteger count) { NSMutableString *trace = [NSMutableString string]; for (NSUInteger i = 0; i < count; i++) { // Newline between all the lines if (i) { [trace appendString:@"\n"]; } NSString *fileName = nil; if (descs[i].filename) { fileName = [NSString stringWithCString:descs[i].filename encoding:NSUTF8StringEncoding]; fileName = [fileName lastPathComponent]; } else { fileName = @"??"; } if (descs[i].class_name) { [trace appendFormat:@"#%-2lu %-35s %#0*lX %s[%s %s]", (unsigned long)i, [fileName UTF8String], // sizeof(void*) * 2 is the length of the hex address (32 vs 64) and + 2 // for the 0x prefix (int)(sizeof(void *) * 2 + 2), (unsigned long)descs[i].address, (descs[i].is_class_method ? "+" : "-"), descs[i].class_name, (descs[i].symbol ? descs[i].symbol : "??")]; } else { [trace appendFormat:@"#%-2lu %-35s %#0*lX %s()", (unsigned long)i, [fileName UTF8String], // sizeof(void*) * 2 is the length of the hex address (32 vs 64) and + 2 // for the 0x prefix (int)(sizeof(void *) * 2 + 2), (unsigned long)descs[i].address, (descs[i].symbol ? descs[i].symbol : "??")]; } } return trace; } #pragma mark Public functions #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 // Before 10.5, we have to do this ourselves. 10.5 adds // +[NSThread callStackReturnAddresses]. // Structure representing a small portion of a stack, starting from the saved // frame pointer, and continuing through the saved program counter. struct GTMStackFrame { void *saved_fp; #if defined (__ppc__) || defined(__ppc64__) void *padding; #endif void *saved_pc; }; // __builtin_frame_address(0) is a gcc builtin that returns a pointer to the // current frame pointer. We then use the frame pointer to walk the stack // picking off program counters and other saved frame pointers. This works // great on i386, but PPC requires a little more work because the PC (or link // register) isn't always stored on the stack. // NSUInteger GTMGetStackProgramCounters(void *outPcs[], NSUInteger count) { if (!outPcs || (count < 1)) return 0; struct GTMStackFrame *fp; #if defined (__ppc__) || defined(__ppc64__) outPcs[0] = __builtin_return_address(0); fp = (struct GTMStackFrame *)__builtin_frame_address(1); #elif defined (__i386__) || defined(__x86_64__) fp = (struct GTMStackFrame *)__builtin_frame_address(0); #else #error architecture not supported #endif NSUInteger level = 0; while (level < count) { if (fp == NULL) { level--; break; } outPcs[level] = fp->saved_pc; level++; fp = (struct GTMStackFrame *)fp->saved_fp; } return level; } #endif // MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 NSUInteger GTMGetStackAddressDescriptors(struct GTMAddressDescriptor outDescs[], NSUInteger count) { if (count < 1 || !outDescs) return 0; NSUInteger result = 0; #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 // Before 10.5, we collect the stack ourselves. void **pcs = calloc(count, sizeof(void*)); if (!pcs) return 0; NSUInteger newSize = GTMGetStackProgramCounters(pcs, count); result = GTMGetStackAddressDescriptorsForAddresses(pcs, outDescs, newSize); free(pcs); #else // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 // Use +[NSThread callStackReturnAddresses] NSArray *addresses = [NSThread callStackReturnAddresses]; NSUInteger addrCount = [addresses count]; if (addrCount) { void **pcs = calloc(addrCount, sizeof(void*)); if (pcs) { void **pcsScanner = pcs; for (NSNumber *address in addresses) { NSUInteger addr = [address unsignedIntegerValue]; *pcsScanner = (void *)addr; ++pcsScanner; } if (count < addrCount) { addrCount = count; } // Fill in the desc structures result = GTMGetStackAddressDescriptorsForAddresses(pcs, outDescs, addrCount); } free(pcs); } #endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 return result; } NSString *GTMStackTrace(void) { // If we don't have enough frames, return an empty string NSString *result = @""; #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 // Before 10.5, we collect the stack ourselves. // The maximum number of stack frames that we will walk. We limit this so // that super-duper recursive functions (or bugs) don't send us for an // infinite loop. struct GTMAddressDescriptor descs[100]; size_t depth = sizeof(descs) / sizeof(struct GTMAddressDescriptor); depth = GTMGetStackAddressDescriptors(descs, depth); // Start at the second item so that GTMStackTrace and it's utility calls (of // which there is currently 1) is not included in the output. const size_t kTracesToStrip = 2; if (depth > kTracesToStrip) { result = GTMStackTraceFromAddressDescriptors(&descs[kTracesToStrip], (depth - kTracesToStrip)); } #else // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 // Use +[NSThread callStackReturnAddresses] NSArray *addresses = [NSThread callStackReturnAddresses]; NSUInteger count = [addresses count]; if (count) { void **pcs = calloc(count, sizeof(void*)); struct GTMAddressDescriptor *descs = calloc(count, sizeof(struct GTMAddressDescriptor)); if (pcs && descs) { void **pcsScanner = pcs; for (NSNumber *address in addresses) { NSUInteger addr = [address unsignedIntegerValue]; *pcsScanner = (void *)addr; ++pcsScanner; } // Fill in the desc structures count = GTMGetStackAddressDescriptorsForAddresses(pcs, descs, count); // Build the trace // We skip 1 frame because the +[NSThread callStackReturnAddresses] will // start w/ this frame. const size_t kTracesToStrip = 1; if (count > kTracesToStrip) { result = GTMStackTraceFromAddressDescriptors(&descs[kTracesToStrip], (count - kTracesToStrip)); } } free(pcs); free(descs); } #endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 return result; } #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 || \ (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && \ (__IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_2_0)) NSString *GTMStackTraceFromException(NSException *e) { NSString *trace = @""; // collect the addresses NSArray *addresses = [e callStackReturnAddresses]; NSUInteger count = [addresses count]; if (count) { void **pcs = calloc(count, sizeof(void*)); struct GTMAddressDescriptor *descs = calloc(count, sizeof(struct GTMAddressDescriptor)); if (pcs && descs) { void **pcsScanner = pcs; for (NSNumber *address in addresses) { NSUInteger addr = [address unsignedIntegerValue]; *pcsScanner = (void *)addr; ++pcsScanner; } // Fill in the desc structures count = GTMGetStackAddressDescriptorsForAddresses(pcs, descs, count); // Build the trace trace = GTMStackTraceFromAddressDescriptors(descs, count); } free(pcs); free(descs); } return trace; } #endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 //__IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_2_0