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); | |
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
| |
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]); |
Mark Mentovai
2011/08/28 15:40:01
As ZombieRecord grows, it may begin to make sense
Scott Hess - ex-Googler
2011/08/29 20:13:36
To my mind thread-safety argues against that. Or
| |
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) { |
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.
| |
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:"; | |
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.
| |
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); | |
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
| |
293 | |
294 // Log -dealloc backtrace in debug builds then crash with a useful | |
295 // stack trace. | |
296 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.
| |
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 |