/core/externals/google-toolbox-for-mac/Foundation/GTMStackTrace.m
Objective C | 386 lines | 285 code | 39 blank | 62 comment | 58 complexity | 749fe5b5b59c01726e67c37bced77ae3 MD5 | raw file
1// 2// GTMStackTrace.m 3// 4// Copyright 2007-2008 Google Inc. 5// 6// Licensed under the Apache License, Version 2.0 (the "License"); you may not 7// use this file except in compliance with the License. You may obtain a copy 8// of the License at 9// 10// http://www.apache.org/licenses/LICENSE-2.0 11// 12// Unless required by applicable law or agreed to in writing, software 13// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15// License for the specific language governing permissions and limitations under 16// the License. 17// 18 19#include <stdlib.h> 20#include <dlfcn.h> 21#include <mach-o/nlist.h> 22#include "GTMStackTrace.h" 23#include "GTMObjC2Runtime.h" 24 25struct GTMClassDescription { 26 const char *class_name; 27 Method *class_methods; 28 unsigned int class_method_count; 29 Method *instance_methods; 30 unsigned int instance_method_count; 31}; 32 33#pragma mark Private utility functions 34 35static struct GTMClassDescription *GTMClassDescriptions(NSUInteger *total_count) { 36 int class_count = objc_getClassList(nil, 0); 37 struct GTMClassDescription *class_descs 38 = calloc(class_count, sizeof(struct GTMClassDescription)); 39 if (class_descs) { 40 Class *classes = calloc(class_count, sizeof(Class)); 41 if (classes) { 42 objc_getClassList(classes, class_count); 43 for (int i = 0; i < class_count; ++i) { 44 class_descs[i].class_methods 45 = class_copyMethodList(object_getClass(classes[i]), 46 &class_descs[i].class_method_count); 47 class_descs[i].instance_methods 48 = class_copyMethodList(classes[i], 49 &class_descs[i].instance_method_count); 50 class_descs[i].class_name = class_getName(classes[i]); 51 } 52 free(classes); 53 } else { 54 // COV_NF_START - Don't know how to force this in a unittest 55 free(class_descs); 56 class_descs = NULL; 57 class_count = 0; 58 // COV_NF_END 59 } 60 } 61 if (total_count) { 62 *total_count = class_count; 63 } 64 return class_descs; 65} 66 67static void GTMFreeClassDescriptions(struct GTMClassDescription *class_descs, 68 NSUInteger count) { 69 if (!class_descs) return; 70 for (NSUInteger i = 0; i < count; ++i) { 71 if (class_descs[i].instance_methods) { 72 free(class_descs[i].instance_methods); 73 } 74 if (class_descs[i].class_methods) { 75 free(class_descs[i].class_methods); 76 } 77 } 78 free(class_descs); 79} 80 81static NSUInteger GTMGetStackAddressDescriptorsForAddresses(void *pcs[], 82 struct GTMAddressDescriptor outDescs[], 83 NSUInteger count) { 84 if (count < 1 || !pcs || !outDescs) return 0; 85 86 NSUInteger class_desc_count; 87 88 // Get our obj-c class descriptions. This is expensive, so we do it once 89 // at the top. We go through this because dladdr doesn't work with 90 // obj methods. 91 struct GTMClassDescription *class_descs 92 = GTMClassDescriptions(&class_desc_count); 93 if (class_descs == NULL) { 94 class_desc_count = 0; 95 } 96 97 // Iterate through the stack. 98 for (NSUInteger i = 0; i < count; ++i) { 99 const char *class_name = NULL; 100 BOOL is_class_method = NO; 101 size_t smallest_diff = SIZE_MAX; 102 struct GTMAddressDescriptor *currDesc = &outDescs[i]; 103 currDesc->address = pcs[i]; 104 Method best_method = NULL; 105 // Iterate through all the classes we know of. 106 for (NSUInteger j = 0; j < class_desc_count; ++j) { 107 // First check the class methods. 108 for (NSUInteger k = 0; k < class_descs[j].class_method_count; ++k) { 109 void *imp = (void *)method_getImplementation(class_descs[j].class_methods[k]); 110 if (imp <= currDesc->address) { 111 size_t diff = (size_t)currDesc->address - (size_t)imp; 112 if (diff < smallest_diff) { 113 best_method = class_descs[j].class_methods[k]; 114 class_name = class_descs[j].class_name; 115 is_class_method = YES; 116 smallest_diff = diff; 117 } 118 } 119 } 120 // Then check the instance methods. 121 for (NSUInteger k = 0; k < class_descs[j].instance_method_count; ++k) { 122 void *imp = (void *)method_getImplementation(class_descs[j].instance_methods[k]); 123 if (imp <= currDesc->address) { 124 size_t diff = (size_t)currDesc->address - (size_t)imp; 125 if (diff < smallest_diff) { 126 best_method = class_descs[j].instance_methods[k]; 127 class_name = class_descs[j].class_name; 128 is_class_method = NO; 129 smallest_diff = diff; 130 } 131 } 132 } 133 } 134 135 // If we have one, store it off. 136 if (best_method) { 137 currDesc->symbol = sel_getName(method_getName(best_method)); 138 currDesc->is_class_method = is_class_method; 139 currDesc->class_name = class_name; 140 } 141 Dl_info info = { NULL, NULL, NULL, NULL }; 142 143 // Check to see if the one returned by dladdr is better. 144 dladdr(currDesc->address, &info); 145 if ((size_t)currDesc->address - (size_t)info.dli_saddr < smallest_diff) { 146 currDesc->symbol = info.dli_sname; 147 currDesc->is_class_method = NO; 148 currDesc->class_name = NULL; 149 } 150 currDesc->filename = info.dli_fname; 151 if (!currDesc->symbol) { 152 currDesc->symbol = "???"; 153 currDesc->is_class_method = NO; 154 currDesc->class_name = NULL; 155 } 156 } 157 GTMFreeClassDescriptions(class_descs, class_desc_count); 158 return count; 159} 160 161static NSString *GTMStackTraceFromAddressDescriptors(struct GTMAddressDescriptor descs[], 162 NSUInteger count) { 163 NSMutableString *trace = [NSMutableString string]; 164 165 for (NSUInteger i = 0; i < count; i++) { 166 // Newline between all the lines 167 if (i) { 168 [trace appendString:@"\n"]; 169 } 170 NSString *fileName = nil; 171 if (descs[i].filename) { 172 fileName = [NSString stringWithCString:descs[i].filename 173 encoding:NSUTF8StringEncoding]; 174 fileName = [fileName lastPathComponent]; 175 } else { 176 fileName = @"??"; 177 } 178 if (descs[i].class_name) { 179 [trace appendFormat:@"#%-2lu %-35s %#0*lX %s[%s %s]", 180 (unsigned long)i, 181 [fileName UTF8String], 182 // sizeof(void*) * 2 is the length of the hex address (32 vs 64) and + 2 183 // for the 0x prefix 184 (int)(sizeof(void *) * 2 + 2), 185 (unsigned long)descs[i].address, 186 (descs[i].is_class_method ? "+" : "-"), 187 descs[i].class_name, 188 (descs[i].symbol ? descs[i].symbol : "??")]; 189 } else { 190 [trace appendFormat:@"#%-2lu %-35s %#0*lX %s()", 191 (unsigned long)i, 192 [fileName UTF8String], 193 // sizeof(void*) * 2 is the length of the hex address (32 vs 64) and + 2 194 // for the 0x prefix 195 (int)(sizeof(void *) * 2 + 2), 196 (unsigned long)descs[i].address, 197 (descs[i].symbol ? descs[i].symbol : "??")]; 198 } 199 } 200 return trace; 201} 202 203#pragma mark Public functions 204 205#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 206// Before 10.5, we have to do this ourselves. 10.5 adds 207// +[NSThread callStackReturnAddresses]. 208 209// Structure representing a small portion of a stack, starting from the saved 210// frame pointer, and continuing through the saved program counter. 211struct GTMStackFrame { 212 void *saved_fp; 213#if defined (__ppc__) || defined(__ppc64__) 214 void *padding; 215#endif 216 void *saved_pc; 217}; 218 219// __builtin_frame_address(0) is a gcc builtin that returns a pointer to the 220// current frame pointer. We then use the frame pointer to walk the stack 221// picking off program counters and other saved frame pointers. This works 222// great on i386, but PPC requires a little more work because the PC (or link 223// register) isn't always stored on the stack. 224// 225NSUInteger GTMGetStackProgramCounters(void *outPcs[], NSUInteger count) { 226 if (!outPcs || (count < 1)) return 0; 227 228 struct GTMStackFrame *fp; 229#if defined (__ppc__) || defined(__ppc64__) 230 outPcs[0] = __builtin_return_address(0); 231 fp = (struct GTMStackFrame *)__builtin_frame_address(1); 232#elif defined (__i386__) || defined(__x86_64__) 233 fp = (struct GTMStackFrame *)__builtin_frame_address(0); 234#else 235#error architecture not supported 236#endif 237 238 NSUInteger level = 0; 239 while (level < count) { 240 if (fp == NULL) { 241 level--; 242 break; 243 } 244 outPcs[level] = fp->saved_pc; 245 level++; 246 fp = (struct GTMStackFrame *)fp->saved_fp; 247 } 248 249 return level; 250} 251#endif // MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 252 253NSUInteger GTMGetStackAddressDescriptors(struct GTMAddressDescriptor outDescs[], 254 NSUInteger count) { 255 if (count < 1 || !outDescs) return 0; 256 NSUInteger result = 0; 257 258#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 259 // Before 10.5, we collect the stack ourselves. 260 261 void **pcs = calloc(count, sizeof(void*)); 262 if (!pcs) return 0; 263 264 NSUInteger newSize = GTMGetStackProgramCounters(pcs, count); 265 266 result = GTMGetStackAddressDescriptorsForAddresses(pcs, outDescs, newSize); 267 free(pcs); 268 269#else // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 270 // Use +[NSThread callStackReturnAddresses] 271 272 NSArray *addresses = [NSThread callStackReturnAddresses]; 273 NSUInteger addrCount = [addresses count]; 274 if (addrCount) { 275 void **pcs = calloc(addrCount, sizeof(void*)); 276 if (pcs) { 277 void **pcsScanner = pcs; 278 for (NSNumber *address in addresses) { 279 NSUInteger addr = [address unsignedIntegerValue]; 280 *pcsScanner = (void *)addr; 281 ++pcsScanner; 282 } 283 if (count < addrCount) { 284 addrCount = count; 285 } 286 // Fill in the desc structures 287 result = GTMGetStackAddressDescriptorsForAddresses(pcs, outDescs, addrCount); 288 } 289 free(pcs); 290 } 291#endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 292 293 return result; 294} 295 296NSString *GTMStackTrace(void) { 297 // If we don't have enough frames, return an empty string 298 NSString *result = @""; 299 300#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 301 // Before 10.5, we collect the stack ourselves. 302 303 // The maximum number of stack frames that we will walk. We limit this so 304 // that super-duper recursive functions (or bugs) don't send us for an 305 // infinite loop. 306 struct GTMAddressDescriptor descs[100]; 307 size_t depth = sizeof(descs) / sizeof(struct GTMAddressDescriptor); 308 depth = GTMGetStackAddressDescriptors(descs, depth); 309 310 // Start at the second item so that GTMStackTrace and it's utility calls (of 311 // which there is currently 1) is not included in the output. 312 const size_t kTracesToStrip = 2; 313 if (depth > kTracesToStrip) { 314 result = GTMStackTraceFromAddressDescriptors(&descs[kTracesToStrip], 315 (depth - kTracesToStrip)); 316 } 317#else // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 318 // Use +[NSThread callStackReturnAddresses] 319 320 NSArray *addresses = [NSThread callStackReturnAddresses]; 321 NSUInteger count = [addresses count]; 322 if (count) { 323 void **pcs = calloc(count, sizeof(void*)); 324 struct GTMAddressDescriptor *descs 325 = calloc(count, sizeof(struct GTMAddressDescriptor)); 326 if (pcs && descs) { 327 void **pcsScanner = pcs; 328 for (NSNumber *address in addresses) { 329 NSUInteger addr = [address unsignedIntegerValue]; 330 *pcsScanner = (void *)addr; 331 ++pcsScanner; 332 } 333 // Fill in the desc structures 334 count = GTMGetStackAddressDescriptorsForAddresses(pcs, descs, count); 335 // Build the trace 336 // We skip 1 frame because the +[NSThread callStackReturnAddresses] will 337 // start w/ this frame. 338 const size_t kTracesToStrip = 1; 339 if (count > kTracesToStrip) { 340 result = GTMStackTraceFromAddressDescriptors(&descs[kTracesToStrip], 341 (count - kTracesToStrip)); 342 } 343 } 344 free(pcs); 345 free(descs); 346 } 347#endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 348 349 return result; 350} 351 352#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 || \ 353 (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && \ 354 (__IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_2_0)) 355 356NSString *GTMStackTraceFromException(NSException *e) { 357 NSString *trace = @""; 358 359 // collect the addresses 360 NSArray *addresses = [e callStackReturnAddresses]; 361 NSUInteger count = [addresses count]; 362 if (count) { 363 void **pcs = calloc(count, sizeof(void*)); 364 struct GTMAddressDescriptor *descs 365 = calloc(count, sizeof(struct GTMAddressDescriptor)); 366 if (pcs && descs) { 367 void **pcsScanner = pcs; 368 for (NSNumber *address in addresses) { 369 NSUInteger addr = [address unsignedIntegerValue]; 370 *pcsScanner = (void *)addr; 371 ++pcsScanner; 372 } 373 // Fill in the desc structures 374 count = GTMGetStackAddressDescriptorsForAddresses(pcs, descs, count); 375 // Build the trace 376 trace = GTMStackTraceFromAddressDescriptors(descs, count); 377 } 378 free(pcs); 379 free(descs); 380 } 381 382 return trace; 383} 384 385#endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 386 //__IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_2_0