1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #import "chrome/common/mac/objc_zombie.h" 6 7 #include <AvailabilityMacros.h> 8 9 #include <execinfo.h> 10 #import <objc/runtime.h> 11 12 #include <algorithm> 13 14 #include "base/debug/crash_logging.h" 15 #include "base/debug/stack_trace.h" 16 #include "base/lazy_instance.h" 17 #include "base/logging.h" 18 #include "base/strings/stringprintf.h" 19 #include "base/synchronization/lock.h" 20 #include "chrome/common/crash_keys.h" 21 #import "chrome/common/mac/objc_method_swizzle.h" 22 23 #if !defined(OS_IOS) && (MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_6) 24 // Apparently objc/runtime.h doesn't define this with the 10.6 SDK yet. 25 // The docs say it exists since 10.6 however. 26 OBJC_EXPORT void *objc_destructInstance(id obj); 27 #endif 28 29 // Deallocated objects are re-classed as |CrZombie|. No superclass 30 // because then the class would have to override many/most of the 31 // inherited methods (|NSObject| is like a category magnet!). 32 // Without the __attribute__, clang's -Wobjc-root-class warns on the missing 33 // superclass. 34 __attribute__((objc_root_class)) 35 @interface CrZombie { 36 Class isa; 37 } 38 @end 39 40 // Objects with enough space are made into "fat" zombies, which 41 // directly remember which class they were until reallocated. 42 @interface CrFatZombie : CrZombie { 43 @public 44 Class wasa; 45 } 46 @end 47 48 namespace { 49 50 // The depth of backtrace to store with zombies. This directly influences 51 // the amount of memory required to track zombies, so should be kept as 52 // small as is useful. Unfortunately, too small and it won't poke through 53 // deep autorelease and event loop stacks. 54 // NOTE(shess): Breakpad currently restricts values to 255 bytes. The 55 // trace is hex-encoded with "0x" prefix and " " separators, meaning 56 // the maximum number of 32-bit items which can be encoded is 23. 57 const size_t kBacktraceDepth = 20; 58 59 // The original implementation for |-[NSObject dealloc]|. 60 IMP g_originalDeallocIMP = NULL; 61 62 // Classes which freed objects become. |g_fatZombieSize| is the 63 // minimum object size which can be made into a fat zombie (which can 64 // remember which class it was before free, even after falling off the 65 // treadmill). 66 Class g_zombieClass = Nil; // cached [CrZombie class] 67 Class g_fatZombieClass = Nil; // cached [CrFatZombie class] 68 size_t g_fatZombieSize = 0; 69 70 // Whether to zombie all freed objects, or only those which return YES 71 // from |-shouldBecomeCrZombie|. 72 BOOL g_zombieAllObjects = NO; 73 74 // Protects |g_zombieCount|, |g_zombieIndex|, and |g_zombies|. 75 base::LazyInstance<base::Lock>::Leaky g_lock = LAZY_INSTANCE_INITIALIZER; 76 77 // How many zombies to keep before freeing, and the current head of 78 // the circular buffer. 79 size_t g_zombieCount = 0; 80 size_t g_zombieIndex = 0; 81 82 typedef struct { 83 id object; // The zombied object. 84 Class wasa; // Value of |object->isa| before we replaced it. 85 void* trace[kBacktraceDepth]; // Backtrace at point of deallocation. 86 size_t traceDepth; // Actual depth of trace[]. 87 } ZombieRecord; 88 89 ZombieRecord* g_zombies = NULL; 90 91 // Replacement |-dealloc| which turns objects into zombies and places 92 // them into |g_zombies| to be freed later. 93 void ZombieDealloc(id self, SEL _cmd) { 94 // This code should only be called when it is implementing |-dealloc|. 95 DCHECK_EQ(_cmd, @selector(dealloc)); 96 97 // Use the original |-dealloc| if the object doesn't wish to be 98 // zombied. 99 if (!g_zombieAllObjects && ![self shouldBecomeCrZombie]) { 100 g_originalDeallocIMP(self, _cmd); 101 return; 102 } 103 104 Class wasa = object_getClass(self); 105 const size_t size = class_getInstanceSize(wasa); 106 107 // Destroy the instance by calling C++ destructors and clearing it 108 // to something unlikely to work well if someone references it. 109 // NOTE(shess): |object_dispose()| will call this again when the 110 // zombie falls off the treadmill! But by then |isa| will be a 111 // class without C++ destructors or associative references, so it 112 // won't hurt anything. 113 objc_destructInstance(self); 114 memset(self, '!', size); 115 116 // If the instance is big enough, make it into a fat zombie and have 117 // it remember the old |isa|. Otherwise make it a regular zombie. 118 // Setting |isa| rather than using |object_setClass()| because that 119 // function is implemented with a memory barrier. The runtime's 120 // |_internal_object_dispose()| (in objc-class.m) does this, so it 121 // should be safe (messaging free'd objects shouldn't be expected to 122 // be thread-safe in the first place). 123 #pragma clang diagnostic push // clang warns about direct access to isa. 124 #pragma clang diagnostic ignored "-Wdeprecated-objc-isa-usage" 125 if (size >= g_fatZombieSize) { 126 self->isa = g_fatZombieClass; 127 static_cast<CrFatZombie*>(self)->wasa = wasa; 128 } else { 129 self->isa = g_zombieClass; 130 } 131 #pragma clang diagnostic pop 132 133 // The new record to swap into |g_zombies|. If |g_zombieCount| is 134 // zero, then |self| will be freed immediately. 135 ZombieRecord zombieToFree = {self, wasa}; 136 zombieToFree.traceDepth = 137 std::max(backtrace(zombieToFree.trace, kBacktraceDepth), 0); 138 139 // Don't involve the lock when creating zombies without a treadmill. 140 if (g_zombieCount > 0) { 141 base::AutoLock pin(g_lock.Get()); 142 143 // Check the count again in a thread-safe manner. 144 if (g_zombieCount > 0) { 145 // Put the current object on the treadmill and keep the previous 146 // occupant. 147 std::swap(zombieToFree, g_zombies[g_zombieIndex]); 148 149 // Bump the index forward. 150 g_zombieIndex = (g_zombieIndex + 1) % g_zombieCount; 151 } 152 } 153 154 // Do the free out here to prevent any chance of deadlock. 155 if (zombieToFree.object) 156 object_dispose(zombieToFree.object); 157 } 158 159 // Search the treadmill for |object| and fill in |*record| if found. 160 // Returns YES if found. 161 BOOL GetZombieRecord(id object, ZombieRecord* record) { 162 // Holding the lock is reasonable because this should be fast, and 163 // the process is going to crash presently anyhow. 164 base::AutoLock pin(g_lock.Get()); 165 for (size_t i = 0; i < g_zombieCount; ++i) { 166 if (g_zombies[i].object == object) { 167 *record = g_zombies[i]; 168 return YES; 169 } 170 } 171 return NO; 172 } 173 174 // Dump the symbols. This is pulled out into a function to make it 175 // easy to use DCHECK to dump only in debug builds. 176 BOOL DumpDeallocTrace(const void* const* array, int size) { 177 fprintf(stderr, "Backtrace from -dealloc:\n"); 178 base::debug::StackTrace(array, size).Print(); 179 180 return YES; 181 } 182 183 // Log a message to a freed object. |wasa| is the object's original 184 // class. |aSelector| is the selector which the calling code was 185 // attempting to send. |viaSelector| is the selector of the 186 // dispatch-related method which is being invoked to send |aSelector| 187 // (for instance, -respondsToSelector:). 188 void ZombieObjectCrash(id object, SEL aSelector, SEL viaSelector) { 189 ZombieRecord record; 190 BOOL found = GetZombieRecord(object, &record); 191 192 // The object's class can be in the zombie record, but if that is 193 // not available it can also be in the object itself (in most cases). 194 Class wasa = Nil; 195 if (found) { 196 wasa = record.wasa; 197 } else if (object_getClass(object) == g_fatZombieClass) { 198 wasa = static_cast<CrFatZombie*>(object)->wasa; 199 } 200 const char* wasaName = (wasa ? class_getName(wasa) : "<unknown>"); 201 202 std::string aString = base::StringPrintf("Zombie <%s: %p> received -%s", 203 wasaName, object, sel_getName(aSelector)); 204 if (viaSelector != NULL) { 205 const char* viaName = sel_getName(viaSelector); 206 base::StringAppendF(&aString, " (via -%s)", viaName); 207 } 208 209 // Set a value for breakpad to report. 210 base::debug::SetCrashKeyValue(crash_keys::mac::kZombie, aString); 211 212 // Encode trace into a breakpad key. 213 if (found) { 214 base::debug::SetCrashKeyFromAddresses( 215 crash_keys::mac::kZombieTrace, record.trace, record.traceDepth); 216 } 217 218 // Log -dealloc backtrace in debug builds then crash with a useful 219 // stack trace. 220 if (found && record.traceDepth) { 221 DCHECK(DumpDeallocTrace(record.trace, record.traceDepth)); 222 } else { 223 DLOG(INFO) << "Unable to generate backtrace from -dealloc."; 224 } 225 DLOG(FATAL) << aString; 226 227 // This is how about:crash is implemented. Using instead of 228 // |base::debug::BreakDebugger()| or |LOG(FATAL)| to make the top of 229 // stack more immediately obvious in crash dumps. 230 int* zero = NULL; 231 *zero = 0; 232 } 233 234 // Initialize our globals, returning YES on success. 235 BOOL ZombieInit() { 236 static BOOL initialized = NO; 237 if (initialized) 238 return YES; 239 240 Class rootClass = [NSObject class]; 241 g_originalDeallocIMP = 242 class_getMethodImplementation(rootClass, @selector(dealloc)); 243 // objc_getClass() so CrZombie doesn't need +class. 244 g_zombieClass = objc_getClass("CrZombie"); 245 g_fatZombieClass = objc_getClass("CrFatZombie"); 246 g_fatZombieSize = class_getInstanceSize(g_fatZombieClass); 247 248 if (!g_originalDeallocIMP || !g_zombieClass || !g_fatZombieClass) 249 return NO; 250 251 initialized = YES; 252 return YES; 253 } 254 255 } // namespace 256 257 @implementation CrZombie 258 259 // The Objective-C runtime needs to be able to call this successfully. 260 + (void)initialize { 261 } 262 263 // Any method not explicitly defined will end up here, forcing a 264 // crash. 265 - (id)forwardingTargetForSelector:(SEL)aSelector { 266 ZombieObjectCrash(self, aSelector, NULL); 267 return nil; 268 } 269 270 // Override a few methods often used for dynamic dispatch to log the 271 // message the caller is attempting to send, rather than the utility 272 // method being used to send it. 273 - (BOOL)respondsToSelector:(SEL)aSelector { 274 ZombieObjectCrash(self, aSelector, _cmd); 275 return NO; 276 } 277 278 - (id)performSelector:(SEL)aSelector { 279 ZombieObjectCrash(self, aSelector, _cmd); 280 return nil; 281 } 282 283 - (id)performSelector:(SEL)aSelector withObject:(id)anObject { 284 ZombieObjectCrash(self, aSelector, _cmd); 285 return nil; 286 } 287 288 - (id)performSelector:(SEL)aSelector 289 withObject:(id)anObject 290 withObject:(id)anotherObject { 291 ZombieObjectCrash(self, aSelector, _cmd); 292 return nil; 293 } 294 295 - (void)performSelector:(SEL)aSelector 296 withObject:(id)anArgument 297 afterDelay:(NSTimeInterval)delay { 298 ZombieObjectCrash(self, aSelector, _cmd); 299 } 300 301 @end 302 303 @implementation CrFatZombie 304 305 // This implementation intentionally left empty. 306 307 @end 308 309 @implementation NSObject (CrZombie) 310 311 - (BOOL)shouldBecomeCrZombie { 312 return NO; 313 } 314 315 @end 316 317 namespace ObjcEvilDoers { 318 319 bool ZombieEnable(bool zombieAllObjects, 320 size_t zombieCount) { 321 // Only allow enable/disable on the main thread, just to keep things 322 // simple. 323 DCHECK([NSThread isMainThread]); 324 325 if (!ZombieInit()) 326 return false; 327 328 g_zombieAllObjects = zombieAllObjects; 329 330 // Replace the implementation of -[NSObject dealloc]. 331 Method m = class_getInstanceMethod([NSObject class], @selector(dealloc)); 332 if (!m) 333 return false; 334 335 const IMP prevDeallocIMP = method_setImplementation(m, (IMP)ZombieDealloc); 336 DCHECK(prevDeallocIMP == g_originalDeallocIMP || 337 prevDeallocIMP == (IMP)ZombieDealloc); 338 339 // Grab the current set of zombies. This is thread-safe because 340 // only the main thread can change these. 341 const size_t oldCount = g_zombieCount; 342 ZombieRecord* oldZombies = g_zombies; 343 344 { 345 base::AutoLock pin(g_lock.Get()); 346 347 // Save the old index in case zombies need to be transferred. 348 size_t oldIndex = g_zombieIndex; 349 350 // Create the new zombie treadmill, disabling zombies in case of 351 // failure. 352 g_zombieIndex = 0; 353 g_zombieCount = zombieCount; 354 g_zombies = NULL; 355 if (g_zombieCount) { 356 g_zombies = 357 static_cast<ZombieRecord*>(calloc(g_zombieCount, sizeof(*g_zombies))); 358 if (!g_zombies) { 359 NOTREACHED(); 360 g_zombies = oldZombies; 361 g_zombieCount = oldCount; 362 g_zombieIndex = oldIndex; 363 ZombieDisable(); 364 return false; 365 } 366 } 367 368 // If the count is changing, allow some of the zombies to continue 369 // shambling forward. 370 const size_t sharedCount = std::min(oldCount, zombieCount); 371 if (sharedCount) { 372 // Get index of the first shared zombie. 373 oldIndex = (oldIndex + oldCount - sharedCount) % oldCount; 374 375 for (; g_zombieIndex < sharedCount; ++ g_zombieIndex) { 376 DCHECK_LT(g_zombieIndex, g_zombieCount); 377 DCHECK_LT(oldIndex, oldCount); 378 std::swap(g_zombies[g_zombieIndex], oldZombies[oldIndex]); 379 oldIndex = (oldIndex + 1) % oldCount; 380 } 381 g_zombieIndex %= g_zombieCount; 382 } 383 } 384 385 // Free the old treadmill and any remaining zombies. 386 if (oldZombies) { 387 for (size_t i = 0; i < oldCount; ++i) { 388 if (oldZombies[i].object) 389 object_dispose(oldZombies[i].object); 390 } 391 free(oldZombies); 392 } 393 394 return true; 395 } 396 397 void ZombieDisable() { 398 // Only allow enable/disable on the main thread, just to keep things 399 // simple. 400 DCHECK([NSThread isMainThread]); 401 402 // |ZombieInit()| was never called. 403 if (!g_originalDeallocIMP) 404 return; 405 406 // Put back the original implementation of -[NSObject dealloc]. 407 Method m = class_getInstanceMethod([NSObject class], @selector(dealloc)); 408 DCHECK(m); 409 method_setImplementation(m, g_originalDeallocIMP); 410 411 // Can safely grab this because it only happens on the main thread. 412 const size_t oldCount = g_zombieCount; 413 ZombieRecord* oldZombies = g_zombies; 414 415 { 416 base::AutoLock pin(g_lock.Get()); // In case any -dealloc are in progress. 417 g_zombieCount = 0; 418 g_zombies = NULL; 419 } 420 421 // Free any remaining zombies. 422 if (oldZombies) { 423 for (size_t i = 0; i < oldCount; ++i) { 424 if (oldZombies[i].object) 425 object_dispose(oldZombies[i].object); 426 } 427 free(oldZombies); 428 } 429 } 430 431 } // namespace ObjcEvilDoers 432