Chromium Code Reviews| Index: chrome/browser/ui/cocoa/objc_zombie.mm |
| diff --git a/chrome/browser/ui/cocoa/objc_zombie.mm b/chrome/browser/ui/cocoa/objc_zombie.mm |
| index 3e097363e031ed24c1ce2f58e37655ba15e56c52..7acb8a27767e7cde94b4694cf254a8d1e06b00d6 100644 |
| --- a/chrome/browser/ui/cocoa/objc_zombie.mm |
| +++ b/chrome/browser/ui/cocoa/objc_zombie.mm |
| @@ -5,11 +5,13 @@ |
| #import "chrome/browser/ui/cocoa/objc_zombie.h" |
| #include <dlfcn.h> |
| +#include <execinfo.h> |
| #include <mach-o/dyld.h> |
| #include <mach-o/nlist.h> |
| #import <objc/objc-class.h> |
| +#include "base/debug/stack_trace.h" |
| #include "base/logging.h" |
| #include "base/mac/mac_util.h" |
| #include "base/metrics/histogram.h" |
| @@ -35,6 +37,12 @@ |
| namespace { |
| +// The depth of backtrace to store with zombies. This directly influences |
| +// the amount of memory required to track zombies, so should be kept as |
| +// small as is useful. Unfortunately, too small and it won't poke through |
| +// deep autorelease and event loop stacks. |
| +const int kBacktraceDepth = 20; |
| + |
| // Function which destroys the contents of an object without freeing |
| // the object. On 10.5 this is |object_cxxDestruct()|, which |
| // traverses the class hierarchy to run the C++ destructors. On 10.6 |
| @@ -71,6 +79,8 @@ size_t g_zombieIndex = 0; |
| typedef struct { |
| id object; // The zombied object. |
| Class wasa; // Value of |object->isa| before we replaced it. |
| + void* trace[kBacktraceDepth]; // Backtrace at point of deallocation. |
| + int traceDepth; // Actual depth of trace[]. |
| } ZombieRecord; |
| ZombieRecord* g_zombies = NULL; |
| @@ -192,6 +202,7 @@ void ZombieDealloc(id self, SEL _cmd) { |
| // The new record to swap into |g_zombies|. If |g_zombieCount| is |
| // zero, then |self| will be freed immediately. |
| ZombieRecord zombieToFree = {self, wasa}; |
| + zombieToFree.traceDepth = backtrace(zombieToFree.trace, kBacktraceDepth); |
|
Mark Mentovai
2011/08/28 15:40:01
Caution!
backtrace may not be implemented in a wa
Scott Hess - ex-Googler
2011/08/29 20:13:36
Hmm. That perhaps argues for taking the overhead
|
| // Don't involve the lock when creating zombies without a treadmill. |
| if (g_zombieCount > 0) { |
| @@ -213,23 +224,28 @@ void ZombieDealloc(id self, SEL _cmd) { |
| object_dispose(zombieToFree.object); |
| } |
| -// Attempt to determine the original class of zombie |object|. |
| -Class ZombieWasa(id object) { |
| - // Fat zombies can hold onto their |wasa| past the point where the |
| - // object was actually freed. Note that to arrive here at all, |
| - // |object|'s memory must still be accessible. |
| - if (object_getClass(object) == g_fatZombieClass) |
| - return static_cast<CrFatZombie*>(object)->wasa; |
| - |
| - // For instances which weren't big enough to store |wasa|, check if |
| - // the object is still on the treadmill. |
| +// Search the treadmill for |object| and fill in |*record| if found. |
| +// Returns YES if found. |
| +BOOL GetZombieRecord(id object, ZombieRecord* record) { |
| + // Holding the lock is reasonable because this should be fast, and |
| + // the process is going to crash presently anyhow. |
| base::AutoLock pin(lock_); |
| for (size_t i=0; i < g_zombieCount; ++i) { |
|
Mark Mentovai
2011/08/28 15:40:01
Style nit (existing code): spaces around the =.
Scott Hess - ex-Googler
2011/08/29 20:13:36
Done.
|
| - if (g_zombies[i].object == object) |
| - return g_zombies[i].wasa; |
| + if (g_zombies[i].object == object) { |
| + *record = g_zombies[i]; |
| + return YES; |
| + } |
| } |
| + return NO; |
| +} |
| + |
| +// Dump the symbols. This is pulled out into a function to make it |
| +// easy to use DCHECK to dump only in debug builds. |
| +BOOL DumpDeallocTrace(const void* const* array, int size) { |
| + DLOG(INFO) << "Backtrace from -dealloc:"; |
|
Mark Mentovai
2011/08/28 15:40:01
base::debug::StackTrace::PrintBacktrace outputs to
Scott Hess - ex-Googler
2011/08/29 20:13:36
Done. But it feels funny.
|
| + base::debug::StackTrace(array, size).PrintBacktrace(); |
| - return Nil; |
| + return YES; |
| } |
| // Log a message to a freed object. |wasa| is the object's original |
| @@ -238,8 +254,19 @@ Class ZombieWasa(id object) { |
| // dispatch-related method which is being invoked to send |aSelector| |
| // (for instance, -respondsToSelector:). |
| void ZombieObjectCrash(id object, SEL aSelector, SEL viaSelector) { |
| - Class wasa = ZombieWasa(object); |
| + ZombieRecord record; |
| + BOOL found = GetZombieRecord(object, &record); |
| + |
| + // The object's class can be in the zombie record, but if that is |
| + // not available it can also be in the object itself (in most cases). |
| + Class wasa = Nil; |
| + if (found) { |
| + wasa = record.wasa; |
| + } else if (object_getClass(object) == g_fatZombieClass) { |
| + wasa = static_cast<CrFatZombie*>(object)->wasa; |
| + } |
| const char* wasaName = (wasa ? class_getName(wasa) : "<unknown>"); |
| + |
| NSString* aString = |
| [NSString stringWithFormat:@"Zombie <%s: %p> received -%s", |
| wasaName, object, sel_getName(aSelector)]; |
| @@ -248,12 +275,33 @@ void ZombieObjectCrash(id object, SEL aSelector, SEL viaSelector) { |
| aString = [aString stringByAppendingFormat:@" (via -%s)", viaName]; |
| } |
| - // Set a value for breakpad to report, then crash. |
| + // Set a value for breakpad to report. |
| SetCrashKeyValue(@"zombie", aString); |
| - LOG(ERROR) << [aString UTF8String]; |
| + |
| + // Hex-encode the backtrace and tuck it into a breakpad key. |
| + NSString* deallocTrace = @"<unknown>"; |
| + if (found && record.traceDepth > 0) { |
| + NSMutableArray* hexBacktrace = |
| + [NSMutableArray arrayWithCapacity:record.traceDepth]; |
| + for (int i = 0; i < record.traceDepth; ++i) { |
| + NSString* s = [NSString stringWithFormat:@"%p", record.trace[i]]; |
| + [hexBacktrace addObject:s]; |
| + } |
| + deallocTrace = [hexBacktrace componentsJoinedByString:@" "]; |
| + } |
| + SetCrashKeyValue(@"zombie_dealloc_bt", deallocTrace); |
|
Mark Mentovai
2011/08/28 15:40:01
Looks like Breakpad keys and values have a maximum
Scott Hess - ex-Googler
2011/08/29 20:13:36
I added some documentation around this to the kBac
|
| + |
| + // Log -dealloc backtrace in debug builds then crash with a useful |
| + // stack trace. |
| + if (found && record.traceDepth) { |
|
Mark Mentovai
2011/08/28 15:40:01
For consistency’s sake, you checked |traceDepth >
Scott Hess - ex-Googler
2011/08/29 20:13:36
Done.
|
| + DCHECK(DumpDeallocTrace(record.trace, record.traceDepth)); |
| + } else { |
| + DLOG(INFO) << "Unable to generate backtrace from -dealloc."; |
| + } |
| + DLOG(FATAL) << [aString UTF8String]; |
| // This is how about:crash is implemented. Using instead of |
| - // |baes::debug::BreakDebugger()| or |LOG(FATAL)| to make the top of |
| + // |base::debug::BreakDebugger()| or |LOG(FATAL)| to make the top of |
| // stack more immediately obvious in crash dumps. |
| int* zero = NULL; |
| *zero = 0; |