| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 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/common/mac/objc_zombie.h" | 5 #import "chrome/common/mac/objc_zombie.h" |
| 6 | 6 |
| 7 #include <AvailabilityMacros.h> |
| 8 |
| 7 #include <dlfcn.h> | 9 #include <dlfcn.h> |
| 8 #include <execinfo.h> | 10 #include <execinfo.h> |
| 9 #include <mach-o/dyld.h> | 11 #include <mach-o/dyld.h> |
| 10 #include <mach-o/nlist.h> | 12 #include <mach-o/nlist.h> |
| 11 | 13 |
| 12 #import <objc/objc-class.h> | 14 #import <objc/objc-class.h> |
| 15 #import <objc/runtime.h> |
| 13 | 16 |
| 14 #include <algorithm> | 17 #include <algorithm> |
| 15 | 18 |
| 16 #include "base/debug/stack_trace.h" | 19 #include "base/debug/stack_trace.h" |
| 17 #include "base/lazy_instance.h" | 20 #include "base/lazy_instance.h" |
| 18 #include "base/logging.h" | 21 #include "base/logging.h" |
| 19 #include "base/mac/crash_logging.h" | 22 #include "base/mac/crash_logging.h" |
| 20 #include "base/mac/mac_util.h" | 23 #include "base/mac/mac_util.h" |
| 21 #include "base/metrics/histogram.h" | 24 #include "base/metrics/histogram.h" |
| 22 #include "base/synchronization/lock.h" | 25 #include "base/synchronization/lock.h" |
| 23 #import "chrome/common/mac/objc_method_swizzle.h" | 26 #import "chrome/common/mac/objc_method_swizzle.h" |
| 24 | 27 |
| 28 #if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_6 |
| 29 // Apparently objc/runtime.h doesn't define this with the 10.6 SDK yet. |
| 30 // The docs say it exists since 10.6 however. |
| 31 OBJC_EXPORT void *objc_destructInstance(id obj); |
| 32 #endif |
| 33 |
| 25 // Deallocated objects are re-classed as |CrZombie|. No superclass | 34 // Deallocated objects are re-classed as |CrZombie|. No superclass |
| 26 // because then the class would have to override many/most of the | 35 // because then the class would have to override many/most of the |
| 27 // inherited methods (|NSObject| is like a category magnet!). | 36 // inherited methods (|NSObject| is like a category magnet!). |
| 28 // Without the __attribute__, clang's -Wobjc-root-class warns on the missing | 37 // Without the __attribute__, clang's -Wobjc-root-class warns on the missing |
| 29 // superclass. | 38 // superclass. |
| 30 __attribute__((objc_root_class)) | 39 __attribute__((objc_root_class)) |
| 31 @interface CrZombie { | 40 @interface CrZombie { |
| 32 Class isa; | 41 Class isa; |
| 33 } | 42 } |
| 34 @end | 43 @end |
| (...skipping 10 matching lines...) Expand all Loading... |
| 45 | 54 |
| 46 // The depth of backtrace to store with zombies. This directly influences | 55 // The depth of backtrace to store with zombies. This directly influences |
| 47 // the amount of memory required to track zombies, so should be kept as | 56 // the amount of memory required to track zombies, so should be kept as |
| 48 // small as is useful. Unfortunately, too small and it won't poke through | 57 // small as is useful. Unfortunately, too small and it won't poke through |
| 49 // deep autorelease and event loop stacks. | 58 // deep autorelease and event loop stacks. |
| 50 // NOTE(shess): Breakpad currently restricts values to 255 bytes. The | 59 // NOTE(shess): Breakpad currently restricts values to 255 bytes. The |
| 51 // trace is hex-encoded with "0x" prefix and " " separators, meaning | 60 // trace is hex-encoded with "0x" prefix and " " separators, meaning |
| 52 // the maximum number of 32-bit items which can be encoded is 23. | 61 // the maximum number of 32-bit items which can be encoded is 23. |
| 53 const size_t kBacktraceDepth = 20; | 62 const size_t kBacktraceDepth = 20; |
| 54 | 63 |
| 55 // Function which destroys the contents of an object without freeing | |
| 56 // the object. On 10.5 this is |object_cxxDestruct()|, which | |
| 57 // traverses the class hierarchy to run the C++ destructors. On 10.6 | |
| 58 // this is |objc_destructInstance()| which calls | |
| 59 // |object_cxxDestruct()| and removes associative references. | |
| 60 // |objc_destructInstance()| returns |void*| but pretending it has no | |
| 61 // return value makes the code simpler. | |
| 62 typedef void DestructFn(id obj); | |
| 63 DestructFn* g_objectDestruct = NULL; | |
| 64 | |
| 65 // The original implementation for |-[NSObject dealloc]|. | 64 // The original implementation for |-[NSObject dealloc]|. |
| 66 IMP g_originalDeallocIMP = NULL; | 65 IMP g_originalDeallocIMP = NULL; |
| 67 | 66 |
| 68 // Classes which freed objects become. |g_fatZombieSize| is the | 67 // Classes which freed objects become. |g_fatZombieSize| is the |
| 69 // minimum object size which can be made into a fat zombie (which can | 68 // minimum object size which can be made into a fat zombie (which can |
| 70 // remember which class it was before free, even after falling off the | 69 // remember which class it was before free, even after falling off the |
| 71 // treadmill). | 70 // treadmill). |
| 72 Class g_zombieClass = Nil; // cached [CrZombie class] | 71 Class g_zombieClass = Nil; // cached [CrZombie class] |
| 73 Class g_fatZombieClass = Nil; // cached [CrFatZombie class] | 72 Class g_fatZombieClass = Nil; // cached [CrFatZombie class] |
| 74 size_t g_fatZombieSize = 0; | 73 size_t g_fatZombieSize = 0; |
| (...skipping 23 matching lines...) Expand all Loading... |
| 98 const void* addr = reinterpret_cast<void*>(&object_getClass); | 97 const void* addr = reinterpret_cast<void*>(&object_getClass); |
| 99 Dl_info info; | 98 Dl_info info; |
| 100 | 99 |
| 101 // |dladdr()| doesn't document how long |info| will stay valid... | 100 // |dladdr()| doesn't document how long |info| will stay valid... |
| 102 if (dladdr(addr, &info)) | 101 if (dladdr(addr, &info)) |
| 103 return info.dli_fname; | 102 return info.dli_fname; |
| 104 | 103 |
| 105 return NULL; | 104 return NULL; |
| 106 } | 105 } |
| 107 | 106 |
| 108 // Lookup |objc_destructInstance()| dynamically because it isn't | |
| 109 // available on 10.5, but we link with the 10.5 SDK. | |
| 110 DestructFn* LookupObjectDestruct_10_6() { | |
| 111 const char* objc_runtime_path = LookupObjcRuntimePath(); | |
| 112 if (!objc_runtime_path) | |
| 113 return NULL; | |
| 114 | |
| 115 void* handle = dlopen(objc_runtime_path, RTLD_LAZY | RTLD_LOCAL); | |
| 116 if (!handle) | |
| 117 return NULL; | |
| 118 | |
| 119 void* fn = dlsym(handle, "objc_destructInstance"); | |
| 120 | |
| 121 // |fn| would normally be expected to become invalid after this | |
| 122 // |dlclose()|, but since the |dlopen()| was on a library | |
| 123 // containing an already-mapped symbol, it will remain valid. | |
| 124 dlclose(handle); | |
| 125 return reinterpret_cast<DestructFn*>(fn); | |
| 126 } | |
| 127 | |
| 128 // Under 10.5 |object_cxxDestruct()| is used but unfortunately it is | |
| 129 // |__private_extern__| in the runtime, meaning |dlsym()| cannot reach it. | |
| 130 DestructFn* LookupObjectDestruct_10_5() { | |
| 131 // |nlist()| is only present for 32-bit. | |
| 132 #if ARCH_CPU_32_BITS | |
| 133 const char* objc_runtime_path = LookupObjcRuntimePath(); | |
| 134 if (!objc_runtime_path) | |
| 135 return NULL; | |
| 136 | |
| 137 struct nlist nl[3]; | |
| 138 bzero(&nl, sizeof(nl)); | |
| 139 | |
| 140 nl[0].n_un.n_name = const_cast<char*>("_object_cxxDestruct"); | |
| 141 | |
| 142 // My ability to calculate the base for offsets is apparently poor. | |
| 143 // Use |class_addIvar| as a known reference point. | |
| 144 nl[1].n_un.n_name = const_cast<char*>("_class_addIvar"); | |
| 145 | |
| 146 if (nlist(objc_runtime_path, nl) < 0 || | |
| 147 nl[0].n_type == N_UNDF || nl[1].n_type == N_UNDF) | |
| 148 return NULL; | |
| 149 | |
| 150 // Back out offset to |class_addIvar()| to get the baseline, then | |
| 151 // add back offset to |object_cxxDestruct()|. | |
| 152 uintptr_t reference_addr = reinterpret_cast<uintptr_t>(&class_addIvar); | |
| 153 reference_addr -= nl[1].n_value; | |
| 154 reference_addr += nl[0].n_value; | |
| 155 | |
| 156 return reinterpret_cast<DestructFn*>(reference_addr); | |
| 157 #endif | |
| 158 | |
| 159 return NULL; | |
| 160 } | |
| 161 | |
| 162 // Replacement |-dealloc| which turns objects into zombies and places | 107 // Replacement |-dealloc| which turns objects into zombies and places |
| 163 // them into |g_zombies| to be freed later. | 108 // them into |g_zombies| to be freed later. |
| 164 void ZombieDealloc(id self, SEL _cmd) { | 109 void ZombieDealloc(id self, SEL _cmd) { |
| 165 // This code should only be called when it is implementing |-dealloc|. | 110 // This code should only be called when it is implementing |-dealloc|. |
| 166 DCHECK_EQ(_cmd, @selector(dealloc)); | 111 DCHECK_EQ(_cmd, @selector(dealloc)); |
| 167 | 112 |
| 168 // Use the original |-dealloc| if the object doesn't wish to be | 113 // Use the original |-dealloc| if the object doesn't wish to be |
| 169 // zombied. | 114 // zombied. |
| 170 if (!g_zombieAllObjects && ![self shouldBecomeCrZombie]) { | 115 if (!g_zombieAllObjects && ![self shouldBecomeCrZombie]) { |
| 171 g_originalDeallocIMP(self, _cmd); | 116 g_originalDeallocIMP(self, _cmd); |
| 172 return; | 117 return; |
| 173 } | 118 } |
| 174 | 119 |
| 175 // Use the original |-dealloc| if |g_objectDestruct| was never | |
| 176 // initialized, because otherwise C++ destructors won't be called. | |
| 177 // This case should be impossible, but doing it wrong would cause | |
| 178 // terrible problems. | |
| 179 DCHECK(g_objectDestruct); | |
| 180 if (!g_objectDestruct) { | |
| 181 g_originalDeallocIMP(self, _cmd); | |
| 182 return; | |
| 183 } | |
| 184 | |
| 185 Class wasa = object_getClass(self); | 120 Class wasa = object_getClass(self); |
| 186 const size_t size = class_getInstanceSize(wasa); | 121 const size_t size = class_getInstanceSize(wasa); |
| 187 | 122 |
| 188 // Destroy the instance by calling C++ destructors and clearing it | 123 // Destroy the instance by calling C++ destructors and clearing it |
| 189 // to something unlikely to work well if someone references it. | 124 // to something unlikely to work well if someone references it. |
| 190 // NOTE(shess): |object_dispose()| will call this again when the | 125 // NOTE(shess): |object_dispose()| will call this again when the |
| 191 // zombie falls off the treadmill! But by then |isa| will be a | 126 // zombie falls off the treadmill! But by then |isa| will be a |
| 192 // class without C++ destructors or associative references, so it | 127 // class without C++ destructors or associative references, so it |
| 193 // won't hurt anything. | 128 // won't hurt anything. |
| 194 (*g_objectDestruct)(self); | 129 objc_destructInstance(self); |
| 195 memset(self, '!', size); | 130 memset(self, '!', size); |
| 196 | 131 |
| 197 // If the instance is big enough, make it into a fat zombie and have | 132 // If the instance is big enough, make it into a fat zombie and have |
| 198 // it remember the old |isa|. Otherwise make it a regular zombie. | 133 // it remember the old |isa|. Otherwise make it a regular zombie. |
| 199 // Setting |isa| rather than using |object_setClass()| because that | 134 // Setting |isa| rather than using |object_setClass()| because that |
| 200 // function is implemented with a memory barrier. The runtime's | 135 // function is implemented with a memory barrier. The runtime's |
| 201 // |_internal_object_dispose()| (in objc-class.m) does this, so it | 136 // |_internal_object_dispose()| (in objc-class.m) does this, so it |
| 202 // should be safe (messaging free'd objects shouldn't be expected to | 137 // should be safe (messaging free'd objects shouldn't be expected to |
| 203 // be thread-safe in the first place). | 138 // be thread-safe in the first place). |
| 204 #pragma clang diagnostic push // clang warns about direct access to isa. | 139 #pragma clang diagnostic push // clang warns about direct access to isa. |
| (...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 306 } | 241 } |
| 307 DLOG(FATAL) << [aString UTF8String]; | 242 DLOG(FATAL) << [aString UTF8String]; |
| 308 | 243 |
| 309 // This is how about:crash is implemented. Using instead of | 244 // This is how about:crash is implemented. Using instead of |
| 310 // |base::debug::BreakDebugger()| or |LOG(FATAL)| to make the top of | 245 // |base::debug::BreakDebugger()| or |LOG(FATAL)| to make the top of |
| 311 // stack more immediately obvious in crash dumps. | 246 // stack more immediately obvious in crash dumps. |
| 312 int* zero = NULL; | 247 int* zero = NULL; |
| 313 *zero = 0; | 248 *zero = 0; |
| 314 } | 249 } |
| 315 | 250 |
| 316 // For monitoring failures in |ZombieInit()|. | |
| 317 enum ZombieFailure { | |
| 318 FAILED_10_5, | |
| 319 FAILED_10_6, | |
| 320 | |
| 321 // Add new versions before here. | |
| 322 FAILED_MAX, | |
| 323 }; | |
| 324 | |
| 325 void RecordZombieFailure(ZombieFailure failure) { | |
| 326 UMA_HISTOGRAM_ENUMERATION("OSX.ZombieInitFailure", failure, FAILED_MAX); | |
| 327 } | |
| 328 | |
| 329 // Initialize our globals, returning YES on success. | 251 // Initialize our globals, returning YES on success. |
| 330 BOOL ZombieInit() { | 252 BOOL ZombieInit() { |
| 331 static BOOL initialized = NO; | 253 static BOOL initialized = NO; |
| 332 if (initialized) | 254 if (initialized) |
| 333 return YES; | 255 return YES; |
| 334 | 256 |
| 335 // Whitelist releases that are compatible with objc zombies. | |
| 336 if (base::mac::IsOSLeopard()) { | |
| 337 g_objectDestruct = LookupObjectDestruct_10_5(); | |
| 338 if (!g_objectDestruct) { | |
| 339 RecordZombieFailure(FAILED_10_5); | |
| 340 return NO; | |
| 341 } | |
| 342 } else if (base::mac::IsOSSnowLeopard()) { | |
| 343 g_objectDestruct = LookupObjectDestruct_10_6(); | |
| 344 if (!g_objectDestruct) { | |
| 345 RecordZombieFailure(FAILED_10_6); | |
| 346 return NO; | |
| 347 } | |
| 348 } else if (base::mac::IsOSLionOrLater()) { | |
| 349 // Assume the future looks like the present. | |
| 350 g_objectDestruct = LookupObjectDestruct_10_6(); | |
| 351 | |
| 352 // Put all future failures into the MAX bin. New OS releases come | |
| 353 // out infrequently enough that this should always correspond to | |
| 354 // "Next release", and once the next release happens that bin will | |
| 355 // get an official name. | |
| 356 if (!g_objectDestruct) { | |
| 357 RecordZombieFailure(FAILED_MAX); | |
| 358 return NO; | |
| 359 } | |
| 360 } else { | |
| 361 return NO; | |
| 362 } | |
| 363 | |
| 364 Class rootClass = [NSObject class]; | 257 Class rootClass = [NSObject class]; |
| 365 g_originalDeallocIMP = | 258 g_originalDeallocIMP = |
| 366 class_getMethodImplementation(rootClass, @selector(dealloc)); | 259 class_getMethodImplementation(rootClass, @selector(dealloc)); |
| 367 // objc_getClass() so CrZombie doesn't need +class. | 260 // objc_getClass() so CrZombie doesn't need +class. |
| 368 g_zombieClass = objc_getClass("CrZombie"); | 261 g_zombieClass = objc_getClass("CrZombie"); |
| 369 g_fatZombieClass = objc_getClass("CrFatZombie"); | 262 g_fatZombieClass = objc_getClass("CrFatZombie"); |
| 370 g_fatZombieSize = class_getInstanceSize(g_fatZombieClass); | 263 g_fatZombieSize = class_getInstanceSize(g_fatZombieClass); |
| 371 | 264 |
| 372 if (!g_objectDestruct || !g_originalDeallocIMP || | 265 if (!g_originalDeallocIMP || !g_zombieClass || !g_fatZombieClass) |
| 373 !g_zombieClass || !g_fatZombieClass) | |
| 374 return NO; | 266 return NO; |
| 375 | 267 |
| 376 initialized = YES; | 268 initialized = YES; |
| 377 return YES; | 269 return YES; |
| 378 } | 270 } |
| 379 | 271 |
| 380 } // namespace | 272 } // namespace |
| 381 | 273 |
| 382 @implementation CrZombie | 274 @implementation CrZombie |
| 383 | 275 |
| (...skipping 163 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 547 if (oldZombies) { | 439 if (oldZombies) { |
| 548 for (size_t i = 0; i < oldCount; ++i) { | 440 for (size_t i = 0; i < oldCount; ++i) { |
| 549 if (oldZombies[i].object) | 441 if (oldZombies[i].object) |
| 550 object_dispose(oldZombies[i].object); | 442 object_dispose(oldZombies[i].object); |
| 551 } | 443 } |
| 552 free(oldZombies); | 444 free(oldZombies); |
| 553 } | 445 } |
| 554 } | 446 } |
| 555 | 447 |
| 556 } // namespace ObjcEvilDoers | 448 } // namespace ObjcEvilDoers |
| OLD | NEW |