Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(77)

Side by Side Diff: chrome/browser/ui/cocoa/objc_zombie.mm

Issue 7766013: [Mac] Capture -dealloc backtrace to log with CrZombie messages. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 9 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« base/debug/stack_trace.cc ('K') | « base/debug/stack_trace.cc ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
OLDNEW
« base/debug/stack_trace.cc ('K') | « base/debug/stack_trace.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698