1 // Copyright (c) 2010 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/browser/ui/cocoa/objc_zombie.h" 6 7 #include <dlfcn.h> 8 #include <mach-o/dyld.h> 9 #include <mach-o/nlist.h> 10 11 #import <objc/objc-class.h> 12 13 #include "base/logging.h" 14 #include "base/synchronization/lock.h" 15 #import "chrome/app/breakpad_mac.h" 16 #import "chrome/browser/ui/cocoa/objc_method_swizzle.h" 17 18 // Deallocated objects are re-classed as |CrZombie|. No superclass 19 // because then the class would have to override many/most of the 20 // inherited methods (|NSObject| is like a category magnet!). 21 @interface CrZombie { 22 Class isa; 23 } 24 @end 25 26 // Objects with enough space are made into "fat" zombies, which 27 // directly remember which class they were until reallocated. 28 @interface CrFatZombie : CrZombie { 29 @public 30 Class wasa; 31 } 32 @end 33 34 namespace { 35 36 // |object_cxxDestruct()| is an Objective-C runtime function which 37 // traverses the object's class tree for ".cxxdestruct" methods which 38 // are run to call C++ destructors as part of |-dealloc|. The 39 // function is not public, so must be looked up using nlist. 40 typedef void DestructFn(id obj); 41 DestructFn* g_object_cxxDestruct = NULL; 42 43 // The original implementation for |-[NSObject dealloc]|. 44 IMP g_originalDeallocIMP = NULL; 45 46 // Classes which freed objects become. |g_fatZombieSize| is the 47 // minimum object size which can be made into a fat zombie (which can 48 // remember which class it was before free, even after falling off the 49 // treadmill). 50 Class g_zombieClass = Nil; // cached [CrZombie class] 51 Class g_fatZombieClass = Nil; // cached [CrFatZombie class] 52 size_t g_fatZombieSize = 0; 53 54 // Whether to zombie all freed objects, or only those which return YES 55 // from |-shouldBecomeCrZombie|. 56 BOOL g_zombieAllObjects = NO; 57 58 // Protects |g_zombieCount|, |g_zombieIndex|, and |g_zombies|. 59 base::Lock lock_; 60 61 // How many zombies to keep before freeing, and the current head of 62 // the circular buffer. 63 size_t g_zombieCount = 0; 64 size_t g_zombieIndex = 0; 65 66 typedef struct { 67 id object; // The zombied object. 68 Class wasa; // Value of |object->isa| before we replaced it. 69 } ZombieRecord; 70 71 ZombieRecord* g_zombies = NULL; 72 73 // Lookup the private |object_cxxDestruct| function and return a 74 // pointer to it. Returns |NULL| on failure. 75 DestructFn* LookupObjectCxxDestruct() { 76 #if ARCH_CPU_64_BITS 77 // TODO(shess): Port to 64-bit. I believe using struct nlist_64 78 // will suffice. http://crbug.com/44021 . 79 NOTIMPLEMENTED(); 80 return NULL; 81 #endif 82 83 struct nlist nl[3]; 84 bzero(&nl, sizeof(nl)); 85 86 nl[0].n_un.n_name = (char*)"_object_cxxDestruct"; 87 88 // My ability to calculate the base for offsets is apparently poor. 89 // Use |class_addIvar| as a known reference point. 90 nl[1].n_un.n_name = (char*)"_class_addIvar"; 91 92 if (nlist("/usr/lib/libobjc.dylib", nl) < 0 || 93 nl[0].n_type == N_UNDF || nl[1].n_type == N_UNDF) 94 return NULL; 95 96 return (DestructFn*)((char*)&class_addIvar - nl[1].n_value + nl[0].n_value); 97 } 98 99 // Replacement |-dealloc| which turns objects into zombies and places 100 // them into |g_zombies| to be freed later. 101 void ZombieDealloc(id self, SEL _cmd) { 102 // This code should only be called when it is implementing |-dealloc|. 103 DCHECK_EQ(_cmd, @selector(dealloc)); 104 105 // Use the original |-dealloc| if the object doesn't wish to be 106 // zombied. 107 if (!g_zombieAllObjects && ![self shouldBecomeCrZombie]) { 108 g_originalDeallocIMP(self, _cmd); 109 return; 110 } 111 112 // Use the original |-dealloc| if |object_cxxDestruct| was never 113 // initialized, because otherwise C++ destructors won't be called. 114 // This case should be impossible, but doing it wrong would cause 115 // terrible problems. 116 DCHECK(g_object_cxxDestruct); 117 if (!g_object_cxxDestruct) { 118 g_originalDeallocIMP(self, _cmd); 119 return; 120 } 121 122 Class wasa = object_getClass(self); 123 const size_t size = class_getInstanceSize(wasa); 124 125 // Destroy the instance by calling C++ destructors and clearing it 126 // to something unlikely to work well if someone references it. 127 (*g_object_cxxDestruct)(self); 128 memset(self, '!', size); 129 130 // If the instance is big enough, make it into a fat zombie and have 131 // it remember the old |isa|. Otherwise make it a regular zombie. 132 // Setting |isa| rather than using |object_setClass()| because that 133 // function is implemented with a memory barrier. The runtime's 134 // |_internal_object_dispose()| (in objc-class.m) does this, so it 135 // should be safe (messaging free'd objects shouldn't be expected to 136 // be thread-safe in the first place). 137 if (size >= g_fatZombieSize) { 138 self->isa = g_fatZombieClass; 139 static_cast<CrFatZombie*>(self)->wasa = wasa; 140 } else { 141 self->isa = g_zombieClass; 142 } 143 144 // The new record to swap into |g_zombies|. If |g_zombieCount| is 145 // zero, then |self| will be freed immediately. 146 ZombieRecord zombieToFree = {self, wasa}; 147 148 // Don't involve the lock when creating zombies without a treadmill. 149 if (g_zombieCount > 0) { 150 base::AutoLock pin(lock_); 151 152 // Check the count again in a thread-safe manner. 153 if (g_zombieCount > 0) { 154 // Put the current object on the treadmill and keep the previous 155 // occupant. 156 std::swap(zombieToFree, g_zombies[g_zombieIndex]); 157 158 // Bump the index forward. 159 g_zombieIndex = (g_zombieIndex + 1) % g_zombieCount; 160 } 161 } 162 163 // Do the free out here to prevent any chance of deadlock. 164 if (zombieToFree.object) 165 free(zombieToFree.object); 166 } 167 168 // Attempt to determine the original class of zombie |object|. 169 Class ZombieWasa(id object) { 170 // Fat zombies can hold onto their |wasa| past the point where the 171 // object was actually freed. Note that to arrive here at all, 172 // |object|'s memory must still be accessible. 173 if (object_getClass(object) == g_fatZombieClass) 174 return static_cast<CrFatZombie*>(object)->wasa; 175 176 // For instances which weren't big enough to store |wasa|, check if 177 // the object is still on the treadmill. 178 base::AutoLock pin(lock_); 179 for (size_t i=0; i < g_zombieCount; ++i) { 180 if (g_zombies[i].object == object) 181 return g_zombies[i].wasa; 182 } 183 184 return Nil; 185 } 186 187 // Log a message to a freed object. |wasa| is the object's original 188 // class. |aSelector| is the selector which the calling code was 189 // attempting to send. |viaSelector| is the selector of the 190 // dispatch-related method which is being invoked to send |aSelector| 191 // (for instance, -respondsToSelector:). 192 void ZombieObjectCrash(id object, SEL aSelector, SEL viaSelector) { 193 Class wasa = ZombieWasa(object); 194 const char* wasaName = (wasa ? class_getName(wasa) : "<unknown>"); 195 NSString* aString = 196 [NSString stringWithFormat:@"Zombie <%s: %p> received -%s", 197 wasaName, object, sel_getName(aSelector)]; 198 if (viaSelector != NULL) { 199 const char* viaName = sel_getName(viaSelector); 200 aString = [aString stringByAppendingFormat:@" (via -%s)", viaName]; 201 } 202 203 // Set a value for breakpad to report, then crash. 204 SetCrashKeyValue(@"zombie", aString); 205 LOG(ERROR) << [aString UTF8String]; 206 207 // This is how about:crash is implemented. Using instead of 208 // |baes::debug::BreakDebugger()| or |LOG(FATAL)| to make the top of 209 // stack more immediately obvious in crash dumps. 210 int* zero = NULL; 211 *zero = 0; 212 } 213 214 // Initialize our globals, returning YES on success. 215 BOOL ZombieInit() { 216 static BOOL initialized = NO; 217 if (initialized) 218 return YES; 219 220 Class rootClass = [NSObject class]; 221 222 g_object_cxxDestruct = LookupObjectCxxDestruct(); 223 g_originalDeallocIMP = 224 class_getMethodImplementation(rootClass, @selector(dealloc)); 225 // objc_getClass() so CrZombie doesn't need +class. 226 g_zombieClass = objc_getClass("CrZombie"); 227 g_fatZombieClass = objc_getClass("CrFatZombie"); 228 g_fatZombieSize = class_getInstanceSize(g_fatZombieClass); 229 230 if (!g_object_cxxDestruct || !g_originalDeallocIMP || 231 !g_zombieClass || !g_fatZombieClass) 232 return NO; 233 234 initialized = YES; 235 return YES; 236 } 237 238 } // namespace 239 240 @implementation CrZombie 241 242 // The Objective-C runtime needs to be able to call this successfully. 243 + (void)initialize { 244 } 245 246 // Any method not explicitly defined will end up here, forcing a 247 // crash. 248 - (id)forwardingTargetForSelector:(SEL)aSelector { 249 ZombieObjectCrash(self, aSelector, NULL); 250 return nil; 251 } 252 253 // Override a few methods often used for dynamic dispatch to log the 254 // message the caller is attempting to send, rather than the utility 255 // method being used to send it. 256 - (BOOL)respondsToSelector:(SEL)aSelector { 257 ZombieObjectCrash(self, aSelector, _cmd); 258 return NO; 259 } 260 261 - (id)performSelector:(SEL)aSelector { 262 ZombieObjectCrash(self, aSelector, _cmd); 263 return nil; 264 } 265 266 - (id)performSelector:(SEL)aSelector withObject:(id)anObject { 267 ZombieObjectCrash(self, aSelector, _cmd); 268 return nil; 269 } 270 271 - (id)performSelector:(SEL)aSelector 272 withObject:(id)anObject 273 withObject:(id)anotherObject { 274 ZombieObjectCrash(self, aSelector, _cmd); 275 return nil; 276 } 277 278 - (void)performSelector:(SEL)aSelector 279 withObject:(id)anArgument 280 afterDelay:(NSTimeInterval)delay { 281 ZombieObjectCrash(self, aSelector, _cmd); 282 } 283 284 @end 285 286 @implementation CrFatZombie 287 288 // This implementation intentionally left empty. 289 290 @end 291 292 @implementation NSObject (CrZombie) 293 294 - (BOOL)shouldBecomeCrZombie { 295 return NO; 296 } 297 298 @end 299 300 namespace ObjcEvilDoers { 301 302 BOOL ZombieEnable(BOOL zombieAllObjects, 303 size_t zombieCount) { 304 // Only allow enable/disable on the main thread, just to keep things 305 // simple. 306 CHECK([NSThread isMainThread]); 307 308 if (!ZombieInit()) 309 return NO; 310 311 g_zombieAllObjects = zombieAllObjects; 312 313 // Replace the implementation of -[NSObject dealloc]. 314 Method m = class_getInstanceMethod([NSObject class], @selector(dealloc)); 315 if (!m) 316 return NO; 317 318 const IMP prevDeallocIMP = method_setImplementation(m, (IMP)ZombieDealloc); 319 DCHECK(prevDeallocIMP == g_originalDeallocIMP || 320 prevDeallocIMP == (IMP)ZombieDealloc); 321 322 // Grab the current set of zombies. This is thread-safe because 323 // only the main thread can change these. 324 const size_t oldCount = g_zombieCount; 325 ZombieRecord* oldZombies = g_zombies; 326 327 { 328 base::AutoLock pin(lock_); 329 330 // Save the old index in case zombies need to be transferred. 331 size_t oldIndex = g_zombieIndex; 332 333 // Create the new zombie treadmill, disabling zombies in case of 334 // failure. 335 g_zombieIndex = 0; 336 g_zombieCount = zombieCount; 337 g_zombies = NULL; 338 if (g_zombieCount) { 339 g_zombies = 340 static_cast<ZombieRecord*>(calloc(g_zombieCount, sizeof(*g_zombies))); 341 if (!g_zombies) { 342 NOTREACHED(); 343 g_zombies = oldZombies; 344 g_zombieCount = oldCount; 345 g_zombieIndex = oldIndex; 346 ZombieDisable(); 347 return NO; 348 } 349 } 350 351 // If the count is changing, allow some of the zombies to continue 352 // shambling forward. 353 const size_t sharedCount = std::min(oldCount, zombieCount); 354 if (sharedCount) { 355 // Get index of the first shared zombie. 356 oldIndex = (oldIndex + oldCount - sharedCount) % oldCount; 357 358 for (; g_zombieIndex < sharedCount; ++ g_zombieIndex) { 359 DCHECK_LT(g_zombieIndex, g_zombieCount); 360 DCHECK_LT(oldIndex, oldCount); 361 std::swap(g_zombies[g_zombieIndex], oldZombies[oldIndex]); 362 oldIndex = (oldIndex + 1) % oldCount; 363 } 364 g_zombieIndex %= g_zombieCount; 365 } 366 } 367 368 // Free the old treadmill and any remaining zombies. 369 if (oldZombies) { 370 for (size_t i = 0; i < oldCount; ++i) { 371 if (oldZombies[i].object) 372 free(oldZombies[i].object); 373 } 374 free(oldZombies); 375 } 376 377 return YES; 378 } 379 380 void ZombieDisable() { 381 // Only allow enable/disable on the main thread, just to keep things 382 // simple. 383 CHECK([NSThread isMainThread]); 384 385 // |ZombieInit()| was never called. 386 if (!g_originalDeallocIMP) 387 return; 388 389 // Put back the original implementation of -[NSObject dealloc]. 390 Method m = class_getInstanceMethod([NSObject class], @selector(dealloc)); 391 CHECK(m); 392 method_setImplementation(m, g_originalDeallocIMP); 393 394 // Can safely grab this because it only happens on the main thread. 395 const size_t oldCount = g_zombieCount; 396 ZombieRecord* oldZombies = g_zombies; 397 398 { 399 base::AutoLock pin(lock_); // In case any |-dealloc| are in-progress. 400 g_zombieCount = 0; 401 g_zombies = NULL; 402 } 403 404 // Free any remaining zombies. 405 if (oldZombies) { 406 for (size_t i = 0; i < oldCount; ++i) { 407 if (oldZombies[i].object) 408 free(oldZombies[i].object); 409 } 410 free(oldZombies); 411 } 412 } 413 414 } // namespace ObjcEvilDoers 415