Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #import "chrome/browser/ui/cocoa/objc_zombie.h" | 5 #import "chrome/browser/ui/cocoa/objc_zombie.h" |
| 6 | 6 |
| 7 #include <dlfcn.h> | 7 #include <dlfcn.h> |
| 8 #include <mach-o/dyld.h> | 8 #include <mach-o/dyld.h> |
| 9 #include <mach-o/nlist.h> | 9 #include <mach-o/nlist.h> |
| 10 | 10 |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 26 // Objects with enough space are made into "fat" zombies, which | 26 // Objects with enough space are made into "fat" zombies, which |
| 27 // directly remember which class they were until reallocated. | 27 // directly remember which class they were until reallocated. |
| 28 @interface CrFatZombie : CrZombie { | 28 @interface CrFatZombie : CrZombie { |
| 29 @public | 29 @public |
| 30 Class wasa; | 30 Class wasa; |
| 31 } | 31 } |
| 32 @end | 32 @end |
| 33 | 33 |
| 34 namespace { | 34 namespace { |
| 35 | 35 |
| 36 // |object_cxxDestruct()| is an Objective-C runtime function which | 36 // Function which destroys the contents of an object without freeing |
| 37 // traverses the object's class tree for ".cxxdestruct" methods which | 37 // the object. On 10.5 this is |object_cxxDestruct()|, which |
| 38 // are run to call C++ destructors as part of |-dealloc|. The | 38 // traverses the class hierarchy to run the C++ destructors. On 10.6 |
| 39 // function is not public, so must be looked up using nlist. | 39 // this is |objc_destructInstance()| which calls |
| 40 // |object_cxxDestruct()| and removes associative references. | |
| 41 // |objc_destructInstance()| returns |void*|, pretending it has no | |
| 42 // return value makes the code simpler. | |
| 40 typedef void DestructFn(id obj); | 43 typedef void DestructFn(id obj); |
| 41 DestructFn* g_object_cxxDestruct = NULL; | 44 DestructFn* g_objectDestruct = NULL; |
| 42 | 45 |
| 43 // The original implementation for |-[NSObject dealloc]|. | 46 // The original implementation for |-[NSObject dealloc]|. |
| 44 IMP g_originalDeallocIMP = NULL; | 47 IMP g_originalDeallocIMP = NULL; |
| 45 | 48 |
| 46 // Classes which freed objects become. |g_fatZombieSize| is the | 49 // Classes which freed objects become. |g_fatZombieSize| is the |
| 47 // minimum object size which can be made into a fat zombie (which can | 50 // 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 | 51 // remember which class it was before free, even after falling off the |
| 49 // treadmill). | 52 // treadmill). |
| 50 Class g_zombieClass = Nil; // cached [CrZombie class] | 53 Class g_zombieClass = Nil; // cached [CrZombie class] |
| 51 Class g_fatZombieClass = Nil; // cached [CrFatZombie class] | 54 Class g_fatZombieClass = Nil; // cached [CrFatZombie class] |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 63 size_t g_zombieCount = 0; | 66 size_t g_zombieCount = 0; |
| 64 size_t g_zombieIndex = 0; | 67 size_t g_zombieIndex = 0; |
| 65 | 68 |
| 66 typedef struct { | 69 typedef struct { |
| 67 id object; // The zombied object. | 70 id object; // The zombied object. |
| 68 Class wasa; // Value of |object->isa| before we replaced it. | 71 Class wasa; // Value of |object->isa| before we replaced it. |
| 69 } ZombieRecord; | 72 } ZombieRecord; |
| 70 | 73 |
| 71 ZombieRecord* g_zombies = NULL; | 74 ZombieRecord* g_zombies = NULL; |
| 72 | 75 |
| 73 // Lookup the private |object_cxxDestruct| function and return a | 76 const char* LookupObjcRuntimePath() { |
| 74 // pointer to it. Returns |NULL| on failure. | 77 const void* addr = reinterpret_cast<void*>(&object_getClass); |
| 75 DestructFn* LookupObjectCxxDestruct() { | 78 Dl_info info; |
| 76 #if ARCH_CPU_64_BITS | 79 |
| 77 // TODO(shess): Port to 64-bit. I believe using struct nlist_64 | 80 // |dladdr()| doesn't document how long |info| will stay valid... |
| 78 // will suffice. http://crbug.com/44021 . | 81 if (dladdr(addr, &info)) |
| 79 NOTIMPLEMENTED(); | 82 return info.dli_fname; |
| 83 | |
| 80 return NULL; | 84 return NULL; |
| 81 #endif | 85 } |
| 86 | |
| 87 // Lookup |objc_destructInstance()| dynamically because it isn't | |
| 88 // available on 10.5, but we link with the 10.5 SDK. | |
| 89 DestructFn* LookupObjectDestruct_10_6() { | |
| 90 const char* objcRuntimePath = LookupObjcRuntimePath(); | |
|
Mark Mentovai
2011/06/07 21:42:57
Use C++ naming rules in C++ code: objc_runtime_pat
Scott Hess - ex-Googler
2011/06/07 23:48:21
Done.
| |
| 91 if (!objcRuntimePath) | |
| 92 return NULL; | |
| 93 | |
| 94 void* handle = dlopen(objcRuntimePath, RTLD_LAZY | RTLD_LOCAL); | |
| 95 if (!handle) | |
| 96 return NULL; | |
| 97 | |
| 98 void* fn = dlsym(handle, "objc_destructInstance"); | |
| 99 | |
| 100 // |fn| would normally be expected to become invalid after this | |
| 101 // |dlclose()|, but since the |dlopen()| was on a library | |
| 102 // containing an already-mapped symbol, it will remain valid. | |
| 103 dlclose(handle); | |
| 104 return reinterpret_cast<DestructFn*>(fn); | |
| 105 } | |
| 106 | |
| 107 // Under 10.5 |object_cxxDestruct()| is used, unfortunately it is | |
| 108 // |__private_extern__| in the runtime, meaning |dlsym()| cannot reach it. | |
| 109 DestructFn* LookupObjectDestruct_10_5() { | |
| 110 // |nlist()| is only present for 32-bit. | |
| 111 #if ARCH_CPU_32_BITS | |
| 112 const char* objcRuntimePath = LookupObjcRuntimePath(); | |
| 113 if (!objcRuntimePath) | |
| 114 return NULL; | |
| 82 | 115 |
| 83 struct nlist nl[3]; | 116 struct nlist nl[3]; |
| 84 bzero(&nl, sizeof(nl)); | 117 bzero(&nl, sizeof(nl)); |
| 85 | 118 |
| 86 nl[0].n_un.n_name = (char*)"_object_cxxDestruct"; | 119 nl[0].n_un.n_name = const_cast<char*>("_object_cxxDestruct"); |
| 87 | 120 |
| 88 // My ability to calculate the base for offsets is apparently poor. | 121 // My ability to calculate the base for offsets is apparently poor. |
| 89 // Use |class_addIvar| as a known reference point. | 122 // Use |class_addIvar| as a known reference point. |
| 90 nl[1].n_un.n_name = (char*)"_class_addIvar"; | 123 nl[1].n_un.n_name = const_cast<char*>("_class_addIvar"); |
| 91 | 124 |
| 92 if (nlist("/usr/lib/libobjc.dylib", nl) < 0 || | 125 if (nlist(objcRuntimePath, nl) < 0 || |
| 93 nl[0].n_type == N_UNDF || nl[1].n_type == N_UNDF) | 126 nl[0].n_type == N_UNDF || nl[1].n_type == N_UNDF) |
| 94 return NULL; | 127 return NULL; |
| 95 | 128 |
| 96 return (DestructFn*)((char*)&class_addIvar - nl[1].n_value + nl[0].n_value); | 129 // Back out offset to |class_addIvar()| to get the baseline, then |
| 130 // add back offset to |object_cxxDestruct()|. | |
| 131 uintptr_t referenceAddr = reinterpret_cast<uintptr_t>(&class_addIvar); | |
| 132 referenceAddr -= nl[1].n_value; | |
| 133 referenceAddr += nl[0].n_value; | |
| 134 | |
| 135 return reinterpret_cast<DestructFn*>(referenceAddr); | |
| 136 #endif | |
| 137 | |
| 138 return NULL; | |
| 97 } | 139 } |
| 98 | 140 |
| 99 // Replacement |-dealloc| which turns objects into zombies and places | 141 // Replacement |-dealloc| which turns objects into zombies and places |
| 100 // them into |g_zombies| to be freed later. | 142 // them into |g_zombies| to be freed later. |
| 101 void ZombieDealloc(id self, SEL _cmd) { | 143 void ZombieDealloc(id self, SEL _cmd) { |
| 102 // This code should only be called when it is implementing |-dealloc|. | 144 // This code should only be called when it is implementing |-dealloc|. |
| 103 DCHECK_EQ(_cmd, @selector(dealloc)); | 145 DCHECK_EQ(_cmd, @selector(dealloc)); |
| 104 | 146 |
| 105 // Use the original |-dealloc| if the object doesn't wish to be | 147 // Use the original |-dealloc| if the object doesn't wish to be |
| 106 // zombied. | 148 // zombied. |
| 107 if (!g_zombieAllObjects && ![self shouldBecomeCrZombie]) { | 149 if (!g_zombieAllObjects && ![self shouldBecomeCrZombie]) { |
| 108 g_originalDeallocIMP(self, _cmd); | 150 g_originalDeallocIMP(self, _cmd); |
| 109 return; | 151 return; |
| 110 } | 152 } |
| 111 | 153 |
| 112 // Use the original |-dealloc| if |object_cxxDestruct| was never | 154 // Use the original |-dealloc| if |g_objectDestruct| was never |
| 113 // initialized, because otherwise C++ destructors won't be called. | 155 // initialized, because otherwise C++ destructors won't be called. |
| 114 // This case should be impossible, but doing it wrong would cause | 156 // This case should be impossible, but doing it wrong would cause |
| 115 // terrible problems. | 157 // terrible problems. |
| 116 DCHECK(g_object_cxxDestruct); | 158 DCHECK(g_objectDestruct); |
| 117 if (!g_object_cxxDestruct) { | 159 if (!g_objectDestruct) { |
| 118 g_originalDeallocIMP(self, _cmd); | 160 g_originalDeallocIMP(self, _cmd); |
| 119 return; | 161 return; |
| 120 } | 162 } |
| 121 | 163 |
| 122 Class wasa = object_getClass(self); | 164 Class wasa = object_getClass(self); |
| 123 const size_t size = class_getInstanceSize(wasa); | 165 const size_t size = class_getInstanceSize(wasa); |
| 124 | 166 |
| 125 // Destroy the instance by calling C++ destructors and clearing it | 167 // Destroy the instance by calling C++ destructors and clearing it |
| 126 // to something unlikely to work well if someone references it. | 168 // to something unlikely to work well if someone references it. |
| 127 // NOTE(shess): |object_dispose()| will call |object_cxxDestruct()| | 169 // NOTE(shess): |object_dispose()| will call this again when the |
| 128 // again when the zombie falls off the treadmill! But by then |isa| | 170 // zombie falls off the treadmill! But by then |isa| will be a |
| 129 // will be something without destructors, so it won't hurt anything. | 171 // class without C++ destructors or associative references, so it |
| 130 (*g_object_cxxDestruct)(self); | 172 // won't hurt anything. |
| 173 (*g_objectDestruct)(self); | |
| 131 memset(self, '!', size); | 174 memset(self, '!', size); |
| 132 | 175 |
| 133 // If the instance is big enough, make it into a fat zombie and have | 176 // If the instance is big enough, make it into a fat zombie and have |
| 134 // it remember the old |isa|. Otherwise make it a regular zombie. | 177 // it remember the old |isa|. Otherwise make it a regular zombie. |
| 135 // Setting |isa| rather than using |object_setClass()| because that | 178 // Setting |isa| rather than using |object_setClass()| because that |
| 136 // function is implemented with a memory barrier. The runtime's | 179 // function is implemented with a memory barrier. The runtime's |
| 137 // |_internal_object_dispose()| (in objc-class.m) does this, so it | 180 // |_internal_object_dispose()| (in objc-class.m) does this, so it |
| 138 // should be safe (messaging free'd objects shouldn't be expected to | 181 // should be safe (messaging free'd objects shouldn't be expected to |
| 139 // be thread-safe in the first place). | 182 // be thread-safe in the first place). |
| 140 if (size >= g_fatZombieSize) { | 183 if (size >= g_fatZombieSize) { |
| (...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 208 LOG(ERROR) << [aString UTF8String]; | 251 LOG(ERROR) << [aString UTF8String]; |
| 209 | 252 |
| 210 // This is how about:crash is implemented. Using instead of | 253 // This is how about:crash is implemented. Using instead of |
| 211 // |baes::debug::BreakDebugger()| or |LOG(FATAL)| to make the top of | 254 // |baes::debug::BreakDebugger()| or |LOG(FATAL)| to make the top of |
| 212 // stack more immediately obvious in crash dumps. | 255 // stack more immediately obvious in crash dumps. |
| 213 int* zero = NULL; | 256 int* zero = NULL; |
| 214 *zero = 0; | 257 *zero = 0; |
| 215 } | 258 } |
| 216 | 259 |
| 217 // Initialize our globals, returning YES on success. | 260 // Initialize our globals, returning YES on success. |
| 218 BOOL ZombieInit() { | 261 BOOL ZombieInit(ObjcEvilDoers::Runtime runtime) { |
| 219 static BOOL initialized = NO; | 262 static BOOL initialized = NO; |
| 220 if (initialized) | 263 if (initialized) |
| 221 return YES; | 264 return YES; |
| 222 | 265 |
| 266 // TODO(shess): Since the current function is persistent, calling it | |
| 267 // multiple times with different values will not change the setup. | |
| 268 // For testing purposes it may be worthwhile to reset this function | |
| 269 // on each call. | |
| 270 if (runtime == ObjcEvilDoers::RUNTIME_10_5) { | |
| 271 g_objectDestruct = LookupObjectDestruct_10_5(); | |
| 272 } else if (runtime == ObjcEvilDoers::RUNTIME_10_6) { | |
| 273 g_objectDestruct = LookupObjectDestruct_10_6(); | |
| 274 } else if (runtime == ObjcEvilDoers::RUNTIME_GUESS) { | |
| 275 g_objectDestruct = LookupObjectDestruct_10_6(); | |
| 276 if (!g_objectDestruct) | |
| 277 g_objectDestruct = LookupObjectDestruct_10_5(); | |
| 278 } | |
| 279 | |
| 223 Class rootClass = [NSObject class]; | 280 Class rootClass = [NSObject class]; |
| 224 | |
| 225 g_object_cxxDestruct = LookupObjectCxxDestruct(); | |
| 226 g_originalDeallocIMP = | 281 g_originalDeallocIMP = |
| 227 class_getMethodImplementation(rootClass, @selector(dealloc)); | 282 class_getMethodImplementation(rootClass, @selector(dealloc)); |
| 228 // objc_getClass() so CrZombie doesn't need +class. | 283 // objc_getClass() so CrZombie doesn't need +class. |
| 229 g_zombieClass = objc_getClass("CrZombie"); | 284 g_zombieClass = objc_getClass("CrZombie"); |
| 230 g_fatZombieClass = objc_getClass("CrFatZombie"); | 285 g_fatZombieClass = objc_getClass("CrFatZombie"); |
| 231 g_fatZombieSize = class_getInstanceSize(g_fatZombieClass); | 286 g_fatZombieSize = class_getInstanceSize(g_fatZombieClass); |
| 232 | 287 |
| 233 if (!g_object_cxxDestruct || !g_originalDeallocIMP || | 288 if (!g_objectDestruct || !g_originalDeallocIMP || |
| 234 !g_zombieClass || !g_fatZombieClass) | 289 !g_zombieClass || !g_fatZombieClass) |
| 235 return NO; | 290 return NO; |
| 236 | 291 |
| 237 initialized = YES; | 292 initialized = YES; |
| 238 return YES; | 293 return YES; |
| 239 } | 294 } |
| 240 | 295 |
| 241 } // namespace | 296 } // namespace |
| 242 | 297 |
| 243 @implementation CrZombie | 298 @implementation CrZombie |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 295 @implementation NSObject (CrZombie) | 350 @implementation NSObject (CrZombie) |
| 296 | 351 |
| 297 - (BOOL)shouldBecomeCrZombie { | 352 - (BOOL)shouldBecomeCrZombie { |
| 298 return NO; | 353 return NO; |
| 299 } | 354 } |
| 300 | 355 |
| 301 @end | 356 @end |
| 302 | 357 |
| 303 namespace ObjcEvilDoers { | 358 namespace ObjcEvilDoers { |
| 304 | 359 |
| 305 BOOL ZombieEnable(BOOL zombieAllObjects, | 360 BOOL ZombieEnable(ObjcEvilDoers::Runtime runtime, |
| 361 BOOL zombieAllObjects, | |
| 306 size_t zombieCount) { | 362 size_t zombieCount) { |
| 307 // Only allow enable/disable on the main thread, just to keep things | 363 // Only allow enable/disable on the main thread, just to keep things |
| 308 // simple. | 364 // simple. |
| 309 CHECK([NSThread isMainThread]); | 365 CHECK([NSThread isMainThread]); |
| 310 | 366 |
| 311 if (!ZombieInit()) | 367 if (!ZombieInit(runtime)) |
| 312 return NO; | 368 return NO; |
| 313 | 369 |
| 314 g_zombieAllObjects = zombieAllObjects; | 370 g_zombieAllObjects = zombieAllObjects; |
| 315 | 371 |
| 316 // Replace the implementation of -[NSObject dealloc]. | 372 // Replace the implementation of -[NSObject dealloc]. |
| 317 Method m = class_getInstanceMethod([NSObject class], @selector(dealloc)); | 373 Method m = class_getInstanceMethod([NSObject class], @selector(dealloc)); |
| 318 if (!m) | 374 if (!m) |
| 319 return NO; | 375 return NO; |
| 320 | 376 |
| 321 const IMP prevDeallocIMP = method_setImplementation(m, (IMP)ZombieDealloc); | 377 const IMP prevDeallocIMP = method_setImplementation(m, (IMP)ZombieDealloc); |
| (...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 408 if (oldZombies) { | 464 if (oldZombies) { |
| 409 for (size_t i = 0; i < oldCount; ++i) { | 465 for (size_t i = 0; i < oldCount; ++i) { |
| 410 if (oldZombies[i].object) | 466 if (oldZombies[i].object) |
| 411 object_dispose(oldZombies[i].object); | 467 object_dispose(oldZombies[i].object); |
| 412 } | 468 } |
| 413 free(oldZombies); | 469 free(oldZombies); |
| 414 } | 470 } |
| 415 } | 471 } |
| 416 | 472 |
| 417 } // namespace ObjcEvilDoers | 473 } // namespace ObjcEvilDoers |
| OLD | NEW |