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