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 <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 static const int kBacktraceDepth = 20; | |
|
Mark Mentovai
2011/08/27 00:29:02
Since you’re in an anonymous namespace, the |stati
Scott Hess - ex-Googler
2011/08/27 05:15:40
Done. Doh.
| |
| 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. | |
|
Mark Mentovai
2011/08/27 00:29:02
Wow, zombie memory explosion! How many zombies do
Scott Hess - ex-Googler
2011/08/27 05:15:40
10,000 at this time. From past instrumentation of
| |
| 74 } ZombieRecord; | 83 } ZombieRecord; |
| 75 | 84 |
| 76 ZombieRecord* g_zombies = NULL; | 85 ZombieRecord* g_zombies = NULL; |
| 77 | 86 |
| 78 const char* LookupObjcRuntimePath() { | 87 const char* LookupObjcRuntimePath() { |
| 79 const void* addr = reinterpret_cast<void*>(&object_getClass); | 88 const void* addr = reinterpret_cast<void*>(&object_getClass); |
| 80 Dl_info info; | 89 Dl_info info; |
| 81 | 90 |
| 82 // |dladdr()| doesn't document how long |info| will stay valid... | 91 // |dladdr()| doesn't document how long |info| will stay valid... |
| 83 if (dladdr(addr, &info)) | 92 if (dladdr(addr, &info)) |
| (...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 184 // be thread-safe in the first place). | 193 // be thread-safe in the first place). |
| 185 if (size >= g_fatZombieSize) { | 194 if (size >= g_fatZombieSize) { |
| 186 self->isa = g_fatZombieClass; | 195 self->isa = g_fatZombieClass; |
| 187 static_cast<CrFatZombie*>(self)->wasa = wasa; | 196 static_cast<CrFatZombie*>(self)->wasa = wasa; |
| 188 } else { | 197 } else { |
| 189 self->isa = g_zombieClass; | 198 self->isa = g_zombieClass; |
| 190 } | 199 } |
| 191 | 200 |
| 192 // The new record to swap into |g_zombies|. If |g_zombieCount| is | 201 // The new record to swap into |g_zombies|. If |g_zombieCount| is |
| 193 // zero, then |self| will be freed immediately. | 202 // zero, then |self| will be freed immediately. |
| 194 ZombieRecord zombieToFree = {self, wasa}; | 203 ZombieRecord zombieToFree = {self, wasa, {0}}; |
|
Mark Mentovai
2011/08/27 00:29:02
This zeroes the whole trace array out. Do we need
Scott Hess - ex-Googler
2011/08/27 05:15:40
We did if I wasn't keeping the count :-).
| |
| 204 backtrace(zombieToFree.trace, kBacktraceDepth); | |
|
Mark Mentovai
2011/08/27 00:29:02
I’d feel much more comfortable if you stored the r
Scott Hess - ex-Googler
2011/08/27 05:15:40
OK. I was mainly thinking in terms of whether we
| |
| 195 | 205 |
| 196 // Don't involve the lock when creating zombies without a treadmill. | 206 // Don't involve the lock when creating zombies without a treadmill. |
| 197 if (g_zombieCount > 0) { | 207 if (g_zombieCount > 0) { |
| 198 base::AutoLock pin(lock_); | 208 base::AutoLock pin(lock_); |
| 199 | 209 |
| 200 // Check the count again in a thread-safe manner. | 210 // Check the count again in a thread-safe manner. |
| 201 if (g_zombieCount > 0) { | 211 if (g_zombieCount > 0) { |
| 202 // Put the current object on the treadmill and keep the previous | 212 // Put the current object on the treadmill and keep the previous |
| 203 // occupant. | 213 // occupant. |
| 204 std::swap(zombieToFree, g_zombies[g_zombieIndex]); | 214 std::swap(zombieToFree, g_zombies[g_zombieIndex]); |
| 205 | 215 |
| 206 // Bump the index forward. | 216 // Bump the index forward. |
| 207 g_zombieIndex = (g_zombieIndex + 1) % g_zombieCount; | 217 g_zombieIndex = (g_zombieIndex + 1) % g_zombieCount; |
| 208 } | 218 } |
| 209 } | 219 } |
| 210 | 220 |
| 211 // Do the free out here to prevent any chance of deadlock. | 221 // Do the free out here to prevent any chance of deadlock. |
| 212 if (zombieToFree.object) | 222 if (zombieToFree.object) |
| 213 object_dispose(zombieToFree.object); | 223 object_dispose(zombieToFree.object); |
| 214 } | 224 } |
| 215 | 225 |
| 216 // Attempt to determine the original class of zombie |object|. | 226 // Search the treadmill for |object| and fill in |*record| if found. |
| 217 Class ZombieWasa(id object) { | 227 // Returns YES if found. |
| 218 // Fat zombies can hold onto their |wasa| past the point where the | 228 BOOL GetZombieRecord(id object, ZombieRecord* record) { |
| 219 // object was actually freed. Note that to arrive here at all, | 229 // Holding the lock is reasonable because this should be fast, and |
| 220 // |object|'s memory must still be accessible. | 230 // 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_); | 231 base::AutoLock pin(lock_); |
| 227 for (size_t i=0; i < g_zombieCount; ++i) { | 232 for (size_t i=0; i < g_zombieCount; ++i) { |
| 228 if (g_zombies[i].object == object) | 233 if (g_zombies[i].object == object) { |
| 229 return g_zombies[i].wasa; | 234 *record = g_zombies[i]; |
| 235 return YES; | |
| 236 } | |
| 237 } | |
| 238 return NO; | |
| 239 } | |
| 240 | |
| 241 // Dump the symbols. This is pulled out into a function to make it | |
| 242 // easy to dump only in debug builds. | |
| 243 BOOL DumpDeallocTrace(const void* const* array, int size) { | |
| 244 if (size > 0) { | |
| 245 LOG(ERROR) << "Backtrace from -dealloc:"; | |
| 246 base::debug::StackTrace(array, size).PrintBacktrace(); | |
| 247 } else { | |
| 248 LOG(ERROR) << "Unable to generate backtrace from -dealloc."; | |
| 230 } | 249 } |
| 231 | 250 |
| 232 return Nil; | 251 return YES; |
| 233 } | 252 } |
| 234 | 253 |
| 235 // Log a message to a freed object. |wasa| is the object's original | 254 // Log a message to a freed object. |wasa| is the object's original |
| 236 // class. |aSelector| is the selector which the calling code was | 255 // class. |aSelector| is the selector which the calling code was |
| 237 // attempting to send. |viaSelector| is the selector of the | 256 // attempting to send. |viaSelector| is the selector of the |
| 238 // dispatch-related method which is being invoked to send |aSelector| | 257 // dispatch-related method which is being invoked to send |aSelector| |
| 239 // (for instance, -respondsToSelector:). | 258 // (for instance, -respondsToSelector:). |
| 240 void ZombieObjectCrash(id object, SEL aSelector, SEL viaSelector) { | 259 void ZombieObjectCrash(id object, SEL aSelector, SEL viaSelector) { |
| 241 Class wasa = ZombieWasa(object); | 260 ZombieRecord record; |
| 261 BOOL found = GetZombieRecord(object, &record); | |
| 262 | |
| 263 // The object's class can be in the zombie record, but if that is | |
| 264 // not available it can also be in the object itself (in most cases). | |
| 265 Class wasa = Nil; | |
| 266 if (found) { | |
| 267 wasa = record.wasa; | |
| 268 } else if (object_getClass(object) == g_fatZombieClass) { | |
| 269 wasa = static_cast<CrFatZombie*>(object)->wasa; | |
| 270 } | |
| 242 const char* wasaName = (wasa ? class_getName(wasa) : "<unknown>"); | 271 const char* wasaName = (wasa ? class_getName(wasa) : "<unknown>"); |
| 272 | |
| 243 NSString* aString = | 273 NSString* aString = |
| 244 [NSString stringWithFormat:@"Zombie <%s: %p> received -%s", | 274 [NSString stringWithFormat:@"Zombie <%s: %p> received -%s", |
| 245 wasaName, object, sel_getName(aSelector)]; | 275 wasaName, object, sel_getName(aSelector)]; |
| 246 if (viaSelector != NULL) { | 276 if (viaSelector != NULL) { |
| 247 const char* viaName = sel_getName(viaSelector); | 277 const char* viaName = sel_getName(viaSelector); |
| 248 aString = [aString stringByAppendingFormat:@" (via -%s)", viaName]; | 278 aString = [aString stringByAppendingFormat:@" (via -%s)", viaName]; |
| 249 } | 279 } |
| 250 | 280 |
| 251 // Set a value for breakpad to report, then crash. | 281 // Set a value for breakpad to report. |
| 252 SetCrashKeyValue(@"zombie", aString); | 282 SetCrashKeyValue(@"zombie", aString); |
| 253 LOG(ERROR) << [aString UTF8String]; | 283 |
| 284 // Hex-encode the backtrace and tuck it into a breakpad key. | |
| 285 NSString* deallocTrace = @"<unknown>"; | |
|
Mark Mentovai
2011/08/27 00:29:02
Nit: remember to use this_style names because you’
Scott Hess - ex-Googler
2011/08/27 05:15:40
OK ... as-is, the file is pretty much Objc-style.
| |
| 286 int realDepth = kBacktraceDepth; | |
| 287 if (found) { | |
| 288 // Back out items beyond the end of the stack. | |
| 289 while (realDepth > 0 && !record.trace[realDepth - 1]) { | |
|
Mark Mentovai
2011/08/27 00:29:02
This is what I was talking about before. It’s kind
Scott Hess - ex-Googler
2011/08/27 05:15:40
It's totally cheesy! Be happy I didn't try to del
| |
| 290 --realDepth; | |
| 291 } | |
| 292 | |
| 293 if (realDepth > 0) { | |
| 294 NSMutableArray* hexBacktrace = | |
| 295 [NSMutableArray arrayWithCapacity:realDepth]; | |
| 296 for (int i = 0; i < realDepth; ++i) { | |
| 297 NSString* s = [NSString stringWithFormat:@"%p", record.trace[i]]; | |
| 298 [hexBacktrace addObject:s]; | |
| 299 } | |
| 300 deallocTrace = [hexBacktrace componentsJoinedByString:@" "]; | |
| 301 } | |
| 302 } | |
| 303 SetCrashKeyValue(@"zombie_dealloc_bt", deallocTrace); | |
|
Mark Mentovai
2011/08/27 00:29:02
Might we also want to record the thread that did t
Scott Hess - ex-Googler
2011/08/27 05:15:40
Hmm. Hmmmmmmm. I think I'm willing to leave that
| |
| 304 | |
| 305 // Log -dealloc backtrace in debug builds then crash with a useful | |
| 306 // stack trace. | |
| 307 DCHECK(DumpDeallocTrace(record.trace, realDepth)); | |
| 308 DLOG(FATAL) << [aString UTF8String]; | |
|
Mark Mentovai
2011/08/27 00:29:02
This won’t get a chance to DLOG(FATAL) because the
Scott Hess - ex-Googler
2011/08/27 05:15:40
DumpDeallocTrace() returns YES. What I want is to
| |
| 254 | 309 |
| 255 // This is how about:crash is implemented. Using instead of | 310 // This is how about:crash is implemented. Using instead of |
| 256 // |baes::debug::BreakDebugger()| or |LOG(FATAL)| to make the top of | 311 // |base::debug::BreakDebugger()| or |LOG(FATAL)| to make the top of |
| 257 // stack more immediately obvious in crash dumps. | 312 // stack more immediately obvious in crash dumps. |
| 258 int* zero = NULL; | 313 int* zero = NULL; |
| 259 *zero = 0; | 314 *zero = 0; |
| 260 } | 315 } |
| 261 | 316 |
| 262 // For monitoring failures in |ZombieInit()|. | 317 // For monitoring failures in |ZombieInit()|. |
| 263 enum ZombieFailure { | 318 enum ZombieFailure { |
| 264 FAILED_10_5, | 319 FAILED_10_5, |
| 265 FAILED_10_6, | 320 FAILED_10_6, |
| 266 | 321 |
| (...skipping 226 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 493 if (oldZombies) { | 548 if (oldZombies) { |
| 494 for (size_t i = 0; i < oldCount; ++i) { | 549 for (size_t i = 0; i < oldCount; ++i) { |
| 495 if (oldZombies[i].object) | 550 if (oldZombies[i].object) |
| 496 object_dispose(oldZombies[i].object); | 551 object_dispose(oldZombies[i].object); |
| 497 } | 552 } |
| 498 free(oldZombies); | 553 free(oldZombies); |
| 499 } | 554 } |
| 500 } | 555 } |
| 501 | 556 |
| 502 } // namespace ObjcEvilDoers | 557 } // namespace ObjcEvilDoers |
| OLD | NEW |