// // 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 #include "GTMStackTrace.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 NSUInteger GTMGetStackAddressDescriptors(struct GTMAddressDescriptor outDescs[], NSUInteger count) { if (count < 1 || !outDescs) return 0; NSUInteger result = 0; 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); } return result; } NSString *GTMStackTrace(void) { // If we don't have enough frames, return an empty string NSString *result = @""; 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); } return result; } 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; }