| 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 <execinfo.h> |
| 8 #include <mach-o/dyld.h> | 9 #include <mach-o/dyld.h> |
| 9 #include <mach-o/nlist.h> | 10 #include <mach-o/nlist.h> |
| 10 | 11 |
| 11 #import <objc/objc-class.h> | 12 #import <objc/objc-class.h> |
| 12 | 13 |
| 14 #include "base/debug/stack_trace.h" |
| 13 #include "base/logging.h" | 15 #include "base/logging.h" |
| 14 #include "base/mac/mac_util.h" | 16 #include "base/mac/mac_util.h" |
| 15 #include "base/metrics/histogram.h" | 17 #include "base/metrics/histogram.h" |
| 16 #include "base/synchronization/lock.h" | 18 #include "base/synchronization/lock.h" |
| 17 #import "chrome/app/breakpad_mac.h" | 19 #import "chrome/app/breakpad_mac.h" |
| 18 #import "chrome/browser/ui/cocoa/objc_method_swizzle.h" | 20 #import "chrome/browser/ui/cocoa/objc_method_swizzle.h" |
| 19 | 21 |
| 20 // Deallocated objects are re-classed as |CrZombie|. No superclass | 22 // Deallocated objects are re-classed as |CrZombie|. No superclass |
| 21 // because then the class would have to override many/most of the | 23 // because then the class would have to override many/most of the |
| 22 // inherited methods (|NSObject| is like a category magnet!). | 24 // inherited methods (|NSObject| is like a category magnet!). |
| 23 @interface CrZombie { | 25 @interface CrZombie { |
| 24 Class isa; | 26 Class isa; |
| 25 } | 27 } |
| 26 @end | 28 @end |
| 27 | 29 |
| 28 // Objects with enough space are made into "fat" zombies, which | 30 // Objects with enough space are made into "fat" zombies, which |
| 29 // directly remember which class they were until reallocated. | 31 // directly remember which class they were until reallocated. |
| 30 @interface CrFatZombie : CrZombie { | 32 @interface CrFatZombie : CrZombie { |
| 31 @public | 33 @public |
| 32 Class wasa; | 34 Class wasa; |
| 33 } | 35 } |
| 34 @end | 36 @end |
| 35 | 37 |
| 36 namespace { | 38 namespace { |
| 37 | 39 |
| 40 // The depth of backtrace to store with zombies. This directly influences |
| 41 // the amount of memory required to track zombies, so should be kept as |
| 42 // small as is useful. Unfortunately, too small and it won't poke through |
| 43 // deep autorelease and event loop stacks. |
| 44 const int kBacktraceDepth = 20; |
| 45 |
| 38 // Function which destroys the contents of an object without freeing | 46 // Function which destroys the contents of an object without freeing |
| 39 // the object. On 10.5 this is |object_cxxDestruct()|, which | 47 // the object. On 10.5 this is |object_cxxDestruct()|, which |
| 40 // traverses the class hierarchy to run the C++ destructors. On 10.6 | 48 // traverses the class hierarchy to run the C++ destructors. On 10.6 |
| 41 // this is |objc_destructInstance()| which calls | 49 // this is |objc_destructInstance()| which calls |
| 42 // |object_cxxDestruct()| and removes associative references. | 50 // |object_cxxDestruct()| and removes associative references. |
| 43 // |objc_destructInstance()| returns |void*| but pretending it has no | 51 // |objc_destructInstance()| returns |void*| but pretending it has no |
| 44 // return value makes the code simpler. | 52 // return value makes the code simpler. |
| 45 typedef void DestructFn(id obj); | 53 typedef void DestructFn(id obj); |
| 46 DestructFn* g_objectDestruct = NULL; | 54 DestructFn* g_objectDestruct = NULL; |
| 47 | 55 |
| (...skipping 16 matching lines...) Expand all Loading... |
| 64 base::Lock lock_; | 72 base::Lock lock_; |
| 65 | 73 |
| 66 // How many zombies to keep before freeing, and the current head of | 74 // How many zombies to keep before freeing, and the current head of |
| 67 // the circular buffer. | 75 // the circular buffer. |
| 68 size_t g_zombieCount = 0; | 76 size_t g_zombieCount = 0; |
| 69 size_t g_zombieIndex = 0; | 77 size_t g_zombieIndex = 0; |
| 70 | 78 |
| 71 typedef struct { | 79 typedef struct { |
| 72 id object; // The zombied object. | 80 id object; // The zombied object. |
| 73 Class wasa; // Value of |object->isa| before we replaced it. | 81 Class wasa; // Value of |object->isa| before we replaced it. |
| 82 void* trace[kBacktraceDepth]; // Backtrace at point of deallocation. |
| 83 int traceDepth; // Actual depth of trace[]. |
| 74 } ZombieRecord; | 84 } ZombieRecord; |
| 75 | 85 |
| 76 ZombieRecord* g_zombies = NULL; | 86 ZombieRecord* g_zombies = NULL; |
| 77 | 87 |
| 78 const char* LookupObjcRuntimePath() { | 88 const char* LookupObjcRuntimePath() { |
| 79 const void* addr = reinterpret_cast<void*>(&object_getClass); | 89 const void* addr = reinterpret_cast<void*>(&object_getClass); |
| 80 Dl_info info; | 90 Dl_info info; |
| 81 | 91 |
| 82 // |dladdr()| doesn't document how long |info| will stay valid... | 92 // |dladdr()| doesn't document how long |info| will stay valid... |
| 83 if (dladdr(addr, &info)) | 93 if (dladdr(addr, &info)) |
| (...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 185 if (size >= g_fatZombieSize) { | 195 if (size >= g_fatZombieSize) { |
| 186 self->isa = g_fatZombieClass; | 196 self->isa = g_fatZombieClass; |
| 187 static_cast<CrFatZombie*>(self)->wasa = wasa; | 197 static_cast<CrFatZombie*>(self)->wasa = wasa; |
| 188 } else { | 198 } else { |
| 189 self->isa = g_zombieClass; | 199 self->isa = g_zombieClass; |
| 190 } | 200 } |
| 191 | 201 |
| 192 // The new record to swap into |g_zombies|. If |g_zombieCount| is | 202 // The new record to swap into |g_zombies|. If |g_zombieCount| is |
| 193 // zero, then |self| will be freed immediately. | 203 // zero, then |self| will be freed immediately. |
| 194 ZombieRecord zombieToFree = {self, wasa}; | 204 ZombieRecord zombieToFree = {self, wasa}; |
| 205 zombieToFree.traceDepth = backtrace(zombieToFree.trace, kBacktraceDepth); |
| 195 | 206 |
| 196 // Don't involve the lock when creating zombies without a treadmill. | 207 // Don't involve the lock when creating zombies without a treadmill. |
| 197 if (g_zombieCount > 0) { | 208 if (g_zombieCount > 0) { |
| 198 base::AutoLock pin(lock_); | 209 base::AutoLock pin(lock_); |
| 199 | 210 |
| 200 // Check the count again in a thread-safe manner. | 211 // Check the count again in a thread-safe manner. |
| 201 if (g_zombieCount > 0) { | 212 if (g_zombieCount > 0) { |
| 202 // Put the current object on the treadmill and keep the previous | 213 // Put the current object on the treadmill and keep the previous |
| 203 // occupant. | 214 // occupant. |
| 204 std::swap(zombieToFree, g_zombies[g_zombieIndex]); | 215 std::swap(zombieToFree, g_zombies[g_zombieIndex]); |
| 205 | 216 |
| 206 // Bump the index forward. | 217 // Bump the index forward. |
| 207 g_zombieIndex = (g_zombieIndex + 1) % g_zombieCount; | 218 g_zombieIndex = (g_zombieIndex + 1) % g_zombieCount; |
| 208 } | 219 } |
| 209 } | 220 } |
| 210 | 221 |
| 211 // Do the free out here to prevent any chance of deadlock. | 222 // Do the free out here to prevent any chance of deadlock. |
| 212 if (zombieToFree.object) | 223 if (zombieToFree.object) |
| 213 object_dispose(zombieToFree.object); | 224 object_dispose(zombieToFree.object); |
| 214 } | 225 } |
| 215 | 226 |
| 216 // Attempt to determine the original class of zombie |object|. | 227 // Search the treadmill for |object| and fill in |*record| if found. |
| 217 Class ZombieWasa(id object) { | 228 // Returns YES if found. |
| 218 // Fat zombies can hold onto their |wasa| past the point where the | 229 BOOL GetZombieRecord(id object, ZombieRecord* record) { |
| 219 // object was actually freed. Note that to arrive here at all, | 230 // Holding the lock is reasonable because this should be fast, and |
| 220 // |object|'s memory must still be accessible. | 231 // the process is going to crash presently anyhow. |
| 221 if (object_getClass(object) == g_fatZombieClass) | |
| 222 return static_cast<CrFatZombie*>(object)->wasa; | |
| 223 | |
| 224 // For instances which weren't big enough to store |wasa|, check if | |
| 225 // the object is still on the treadmill. | |
| 226 base::AutoLock pin(lock_); | 232 base::AutoLock pin(lock_); |
| 227 for (size_t i=0; i < g_zombieCount; ++i) { | 233 for (size_t i=0; i < g_zombieCount; ++i) { |
| 228 if (g_zombies[i].object == object) | 234 if (g_zombies[i].object == object) { |
| 229 return g_zombies[i].wasa; | 235 *record = g_zombies[i]; |
| 236 return YES; |
| 237 } |
| 230 } | 238 } |
| 239 return NO; |
| 240 } |
| 231 | 241 |
| 232 return Nil; | 242 // Dump the symbols. This is pulled out into a function to make it |
| 243 // easy to use DCHECK to dump only in debug builds. |
| 244 BOOL DumpDeallocTrace(const void* const* array, int size) { |
| 245 DLOG(INFO) << "Backtrace from -dealloc:"; |
| 246 base::debug::StackTrace(array, size).PrintBacktrace(); |
| 247 |
| 248 return YES; |
| 233 } | 249 } |
| 234 | 250 |
| 235 // Log a message to a freed object. |wasa| is the object's original | 251 // Log a message to a freed object. |wasa| is the object's original |
| 236 // class. |aSelector| is the selector which the calling code was | 252 // class. |aSelector| is the selector which the calling code was |
| 237 // attempting to send. |viaSelector| is the selector of the | 253 // attempting to send. |viaSelector| is the selector of the |
| 238 // dispatch-related method which is being invoked to send |aSelector| | 254 // dispatch-related method which is being invoked to send |aSelector| |
| 239 // (for instance, -respondsToSelector:). | 255 // (for instance, -respondsToSelector:). |
| 240 void ZombieObjectCrash(id object, SEL aSelector, SEL viaSelector) { | 256 void ZombieObjectCrash(id object, SEL aSelector, SEL viaSelector) { |
| 241 Class wasa = ZombieWasa(object); | 257 ZombieRecord record; |
| 258 BOOL found = GetZombieRecord(object, &record); |
| 259 |
| 260 // The object's class can be in the zombie record, but if that is |
| 261 // not available it can also be in the object itself (in most cases). |
| 262 Class wasa = Nil; |
| 263 if (found) { |
| 264 wasa = record.wasa; |
| 265 } else if (object_getClass(object) == g_fatZombieClass) { |
| 266 wasa = static_cast<CrFatZombie*>(object)->wasa; |
| 267 } |
| 242 const char* wasaName = (wasa ? class_getName(wasa) : "<unknown>"); | 268 const char* wasaName = (wasa ? class_getName(wasa) : "<unknown>"); |
| 269 |
| 243 NSString* aString = | 270 NSString* aString = |
| 244 [NSString stringWithFormat:@"Zombie <%s: %p> received -%s", | 271 [NSString stringWithFormat:@"Zombie <%s: %p> received -%s", |
| 245 wasaName, object, sel_getName(aSelector)]; | 272 wasaName, object, sel_getName(aSelector)]; |
| 246 if (viaSelector != NULL) { | 273 if (viaSelector != NULL) { |
| 247 const char* viaName = sel_getName(viaSelector); | 274 const char* viaName = sel_getName(viaSelector); |
| 248 aString = [aString stringByAppendingFormat:@" (via -%s)", viaName]; | 275 aString = [aString stringByAppendingFormat:@" (via -%s)", viaName]; |
| 249 } | 276 } |
| 250 | 277 |
| 251 // Set a value for breakpad to report, then crash. | 278 // Set a value for breakpad to report. |
| 252 SetCrashKeyValue(@"zombie", aString); | 279 SetCrashKeyValue(@"zombie", aString); |
| 253 LOG(ERROR) << [aString UTF8String]; | 280 |
| 281 // Hex-encode the backtrace and tuck it into a breakpad key. |
| 282 NSString* deallocTrace = @"<unknown>"; |
| 283 if (found && record.traceDepth > 0) { |
| 284 NSMutableArray* hexBacktrace = |
| 285 [NSMutableArray arrayWithCapacity:record.traceDepth]; |
| 286 for (int i = 0; i < record.traceDepth; ++i) { |
| 287 NSString* s = [NSString stringWithFormat:@"%p", record.trace[i]]; |
| 288 [hexBacktrace addObject:s]; |
| 289 } |
| 290 deallocTrace = [hexBacktrace componentsJoinedByString:@" "]; |
| 291 } |
| 292 SetCrashKeyValue(@"zombie_dealloc_bt", deallocTrace); |
| 293 |
| 294 // Log -dealloc backtrace in debug builds then crash with a useful |
| 295 // stack trace. |
| 296 if (found && record.traceDepth) { |
| 297 DCHECK(DumpDeallocTrace(record.trace, record.traceDepth)); |
| 298 } else { |
| 299 DLOG(INFO) << "Unable to generate backtrace from -dealloc."; |
| 300 } |
| 301 DLOG(FATAL) << [aString UTF8String]; |
| 254 | 302 |
| 255 // This is how about:crash is implemented. Using instead of | 303 // This is how about:crash is implemented. Using instead of |
| 256 // |baes::debug::BreakDebugger()| or |LOG(FATAL)| to make the top of | 304 // |base::debug::BreakDebugger()| or |LOG(FATAL)| to make the top of |
| 257 // stack more immediately obvious in crash dumps. | 305 // stack more immediately obvious in crash dumps. |
| 258 int* zero = NULL; | 306 int* zero = NULL; |
| 259 *zero = 0; | 307 *zero = 0; |
| 260 } | 308 } |
| 261 | 309 |
| 262 // For monitoring failures in |ZombieInit()|. | 310 // For monitoring failures in |ZombieInit()|. |
| 263 enum ZombieFailure { | 311 enum ZombieFailure { |
| 264 FAILED_10_5, | 312 FAILED_10_5, |
| 265 FAILED_10_6, | 313 FAILED_10_6, |
| 266 | 314 |
| (...skipping 226 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 493 if (oldZombies) { | 541 if (oldZombies) { |
| 494 for (size_t i = 0; i < oldCount; ++i) { | 542 for (size_t i = 0; i < oldCount; ++i) { |
| 495 if (oldZombies[i].object) | 543 if (oldZombies[i].object) |
| 496 object_dispose(oldZombies[i].object); | 544 object_dispose(oldZombies[i].object); |
| 497 } | 545 } |
| 498 free(oldZombies); | 546 free(oldZombies); |
| 499 } | 547 } |
| 500 } | 548 } |
| 501 | 549 |
| 502 } // namespace ObjcEvilDoers | 550 } // namespace ObjcEvilDoers |
| OLD | NEW |