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 |