OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 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/common/mac/objc_zombie.h" | 5 #import "chrome/common/mac/objc_zombie.h" |
6 | 6 |
| 7 #include <AvailabilityMacros.h> |
| 8 |
7 #include <dlfcn.h> | 9 #include <dlfcn.h> |
8 #include <execinfo.h> | 10 #include <execinfo.h> |
9 #include <mach-o/dyld.h> | 11 #include <mach-o/dyld.h> |
10 #include <mach-o/nlist.h> | 12 #include <mach-o/nlist.h> |
11 | 13 |
12 #import <objc/objc-class.h> | 14 #import <objc/objc-class.h> |
| 15 #import <objc/runtime.h> |
13 | 16 |
14 #include <algorithm> | 17 #include <algorithm> |
15 | 18 |
16 #include "base/debug/stack_trace.h" | 19 #include "base/debug/stack_trace.h" |
17 #include "base/lazy_instance.h" | 20 #include "base/lazy_instance.h" |
18 #include "base/logging.h" | 21 #include "base/logging.h" |
19 #include "base/mac/crash_logging.h" | 22 #include "base/mac/crash_logging.h" |
20 #include "base/mac/mac_util.h" | 23 #include "base/mac/mac_util.h" |
21 #include "base/metrics/histogram.h" | 24 #include "base/metrics/histogram.h" |
22 #include "base/synchronization/lock.h" | 25 #include "base/synchronization/lock.h" |
23 #import "chrome/common/mac/objc_method_swizzle.h" | 26 #import "chrome/common/mac/objc_method_swizzle.h" |
24 | 27 |
| 28 #if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_6 |
| 29 // Apparently objc/runtime.h doesn't define this with the 10.6 SDK yet. |
| 30 // The docs say it exists since 10.6 however. |
| 31 OBJC_EXPORT void *objc_destructInstance(id obj); |
| 32 #endif |
| 33 |
25 // Deallocated objects are re-classed as |CrZombie|. No superclass | 34 // Deallocated objects are re-classed as |CrZombie|. No superclass |
26 // because then the class would have to override many/most of the | 35 // because then the class would have to override many/most of the |
27 // inherited methods (|NSObject| is like a category magnet!). | 36 // inherited methods (|NSObject| is like a category magnet!). |
28 // Without the __attribute__, clang's -Wobjc-root-class warns on the missing | 37 // Without the __attribute__, clang's -Wobjc-root-class warns on the missing |
29 // superclass. | 38 // superclass. |
30 __attribute__((objc_root_class)) | 39 __attribute__((objc_root_class)) |
31 @interface CrZombie { | 40 @interface CrZombie { |
32 Class isa; | 41 Class isa; |
33 } | 42 } |
34 @end | 43 @end |
(...skipping 10 matching lines...) Expand all Loading... |
45 | 54 |
46 // The depth of backtrace to store with zombies. This directly influences | 55 // The depth of backtrace to store with zombies. This directly influences |
47 // the amount of memory required to track zombies, so should be kept as | 56 // the amount of memory required to track zombies, so should be kept as |
48 // small as is useful. Unfortunately, too small and it won't poke through | 57 // small as is useful. Unfortunately, too small and it won't poke through |
49 // deep autorelease and event loop stacks. | 58 // deep autorelease and event loop stacks. |
50 // NOTE(shess): Breakpad currently restricts values to 255 bytes. The | 59 // NOTE(shess): Breakpad currently restricts values to 255 bytes. The |
51 // trace is hex-encoded with "0x" prefix and " " separators, meaning | 60 // trace is hex-encoded with "0x" prefix and " " separators, meaning |
52 // the maximum number of 32-bit items which can be encoded is 23. | 61 // the maximum number of 32-bit items which can be encoded is 23. |
53 const size_t kBacktraceDepth = 20; | 62 const size_t kBacktraceDepth = 20; |
54 | 63 |
55 // Function which destroys the contents of an object without freeing | |
56 // the object. On 10.5 this is |object_cxxDestruct()|, which | |
57 // traverses the class hierarchy to run the C++ destructors. On 10.6 | |
58 // this is |objc_destructInstance()| which calls | |
59 // |object_cxxDestruct()| and removes associative references. | |
60 // |objc_destructInstance()| returns |void*| but pretending it has no | |
61 // return value makes the code simpler. | |
62 typedef void DestructFn(id obj); | |
63 DestructFn* g_objectDestruct = NULL; | |
64 | |
65 // The original implementation for |-[NSObject dealloc]|. | 64 // The original implementation for |-[NSObject dealloc]|. |
66 IMP g_originalDeallocIMP = NULL; | 65 IMP g_originalDeallocIMP = NULL; |
67 | 66 |
68 // Classes which freed objects become. |g_fatZombieSize| is the | 67 // Classes which freed objects become. |g_fatZombieSize| is the |
69 // minimum object size which can be made into a fat zombie (which can | 68 // minimum object size which can be made into a fat zombie (which can |
70 // remember which class it was before free, even after falling off the | 69 // remember which class it was before free, even after falling off the |
71 // treadmill). | 70 // treadmill). |
72 Class g_zombieClass = Nil; // cached [CrZombie class] | 71 Class g_zombieClass = Nil; // cached [CrZombie class] |
73 Class g_fatZombieClass = Nil; // cached [CrFatZombie class] | 72 Class g_fatZombieClass = Nil; // cached [CrFatZombie class] |
74 size_t g_fatZombieSize = 0; | 73 size_t g_fatZombieSize = 0; |
(...skipping 23 matching lines...) Expand all Loading... |
98 const void* addr = reinterpret_cast<void*>(&object_getClass); | 97 const void* addr = reinterpret_cast<void*>(&object_getClass); |
99 Dl_info info; | 98 Dl_info info; |
100 | 99 |
101 // |dladdr()| doesn't document how long |info| will stay valid... | 100 // |dladdr()| doesn't document how long |info| will stay valid... |
102 if (dladdr(addr, &info)) | 101 if (dladdr(addr, &info)) |
103 return info.dli_fname; | 102 return info.dli_fname; |
104 | 103 |
105 return NULL; | 104 return NULL; |
106 } | 105 } |
107 | 106 |
108 // Lookup |objc_destructInstance()| dynamically because it isn't | |
109 // available on 10.5, but we link with the 10.5 SDK. | |
110 DestructFn* LookupObjectDestruct_10_6() { | |
111 const char* objc_runtime_path = LookupObjcRuntimePath(); | |
112 if (!objc_runtime_path) | |
113 return NULL; | |
114 | |
115 void* handle = dlopen(objc_runtime_path, RTLD_LAZY | RTLD_LOCAL); | |
116 if (!handle) | |
117 return NULL; | |
118 | |
119 void* fn = dlsym(handle, "objc_destructInstance"); | |
120 | |
121 // |fn| would normally be expected to become invalid after this | |
122 // |dlclose()|, but since the |dlopen()| was on a library | |
123 // containing an already-mapped symbol, it will remain valid. | |
124 dlclose(handle); | |
125 return reinterpret_cast<DestructFn*>(fn); | |
126 } | |
127 | |
128 // Under 10.5 |object_cxxDestruct()| is used but unfortunately it is | |
129 // |__private_extern__| in the runtime, meaning |dlsym()| cannot reach it. | |
130 DestructFn* LookupObjectDestruct_10_5() { | |
131 // |nlist()| is only present for 32-bit. | |
132 #if ARCH_CPU_32_BITS | |
133 const char* objc_runtime_path = LookupObjcRuntimePath(); | |
134 if (!objc_runtime_path) | |
135 return NULL; | |
136 | |
137 struct nlist nl[3]; | |
138 bzero(&nl, sizeof(nl)); | |
139 | |
140 nl[0].n_un.n_name = const_cast<char*>("_object_cxxDestruct"); | |
141 | |
142 // My ability to calculate the base for offsets is apparently poor. | |
143 // Use |class_addIvar| as a known reference point. | |
144 nl[1].n_un.n_name = const_cast<char*>("_class_addIvar"); | |
145 | |
146 if (nlist(objc_runtime_path, nl) < 0 || | |
147 nl[0].n_type == N_UNDF || nl[1].n_type == N_UNDF) | |
148 return NULL; | |
149 | |
150 // Back out offset to |class_addIvar()| to get the baseline, then | |
151 // add back offset to |object_cxxDestruct()|. | |
152 uintptr_t reference_addr = reinterpret_cast<uintptr_t>(&class_addIvar); | |
153 reference_addr -= nl[1].n_value; | |
154 reference_addr += nl[0].n_value; | |
155 | |
156 return reinterpret_cast<DestructFn*>(reference_addr); | |
157 #endif | |
158 | |
159 return NULL; | |
160 } | |
161 | |
162 // Replacement |-dealloc| which turns objects into zombies and places | 107 // Replacement |-dealloc| which turns objects into zombies and places |
163 // them into |g_zombies| to be freed later. | 108 // them into |g_zombies| to be freed later. |
164 void ZombieDealloc(id self, SEL _cmd) { | 109 void ZombieDealloc(id self, SEL _cmd) { |
165 // This code should only be called when it is implementing |-dealloc|. | 110 // This code should only be called when it is implementing |-dealloc|. |
166 DCHECK_EQ(_cmd, @selector(dealloc)); | 111 DCHECK_EQ(_cmd, @selector(dealloc)); |
167 | 112 |
168 // Use the original |-dealloc| if the object doesn't wish to be | 113 // Use the original |-dealloc| if the object doesn't wish to be |
169 // zombied. | 114 // zombied. |
170 if (!g_zombieAllObjects && ![self shouldBecomeCrZombie]) { | 115 if (!g_zombieAllObjects && ![self shouldBecomeCrZombie]) { |
171 g_originalDeallocIMP(self, _cmd); | 116 g_originalDeallocIMP(self, _cmd); |
172 return; | 117 return; |
173 } | 118 } |
174 | 119 |
175 // Use the original |-dealloc| if |g_objectDestruct| was never | |
176 // initialized, because otherwise C++ destructors won't be called. | |
177 // This case should be impossible, but doing it wrong would cause | |
178 // terrible problems. | |
179 DCHECK(g_objectDestruct); | |
180 if (!g_objectDestruct) { | |
181 g_originalDeallocIMP(self, _cmd); | |
182 return; | |
183 } | |
184 | |
185 Class wasa = object_getClass(self); | 120 Class wasa = object_getClass(self); |
186 const size_t size = class_getInstanceSize(wasa); | 121 const size_t size = class_getInstanceSize(wasa); |
187 | 122 |
188 // Destroy the instance by calling C++ destructors and clearing it | 123 // Destroy the instance by calling C++ destructors and clearing it |
189 // to something unlikely to work well if someone references it. | 124 // to something unlikely to work well if someone references it. |
190 // NOTE(shess): |object_dispose()| will call this again when the | 125 // NOTE(shess): |object_dispose()| will call this again when the |
191 // zombie falls off the treadmill! But by then |isa| will be a | 126 // zombie falls off the treadmill! But by then |isa| will be a |
192 // class without C++ destructors or associative references, so it | 127 // class without C++ destructors or associative references, so it |
193 // won't hurt anything. | 128 // won't hurt anything. |
194 (*g_objectDestruct)(self); | 129 objc_destructInstance(self); |
195 memset(self, '!', size); | 130 memset(self, '!', size); |
196 | 131 |
197 // If the instance is big enough, make it into a fat zombie and have | 132 // If the instance is big enough, make it into a fat zombie and have |
198 // it remember the old |isa|. Otherwise make it a regular zombie. | 133 // it remember the old |isa|. Otherwise make it a regular zombie. |
199 // Setting |isa| rather than using |object_setClass()| because that | 134 // Setting |isa| rather than using |object_setClass()| because that |
200 // function is implemented with a memory barrier. The runtime's | 135 // function is implemented with a memory barrier. The runtime's |
201 // |_internal_object_dispose()| (in objc-class.m) does this, so it | 136 // |_internal_object_dispose()| (in objc-class.m) does this, so it |
202 // should be safe (messaging free'd objects shouldn't be expected to | 137 // should be safe (messaging free'd objects shouldn't be expected to |
203 // be thread-safe in the first place). | 138 // be thread-safe in the first place). |
204 #pragma clang diagnostic push // clang warns about direct access to isa. | 139 #pragma clang diagnostic push // clang warns about direct access to isa. |
(...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
306 } | 241 } |
307 DLOG(FATAL) << [aString UTF8String]; | 242 DLOG(FATAL) << [aString UTF8String]; |
308 | 243 |
309 // This is how about:crash is implemented. Using instead of | 244 // This is how about:crash is implemented. Using instead of |
310 // |base::debug::BreakDebugger()| or |LOG(FATAL)| to make the top of | 245 // |base::debug::BreakDebugger()| or |LOG(FATAL)| to make the top of |
311 // stack more immediately obvious in crash dumps. | 246 // stack more immediately obvious in crash dumps. |
312 int* zero = NULL; | 247 int* zero = NULL; |
313 *zero = 0; | 248 *zero = 0; |
314 } | 249 } |
315 | 250 |
316 // For monitoring failures in |ZombieInit()|. | |
317 enum ZombieFailure { | |
318 FAILED_10_5, | |
319 FAILED_10_6, | |
320 | |
321 // Add new versions before here. | |
322 FAILED_MAX, | |
323 }; | |
324 | |
325 void RecordZombieFailure(ZombieFailure failure) { | |
326 UMA_HISTOGRAM_ENUMERATION("OSX.ZombieInitFailure", failure, FAILED_MAX); | |
327 } | |
328 | |
329 // Initialize our globals, returning YES on success. | 251 // Initialize our globals, returning YES on success. |
330 BOOL ZombieInit() { | 252 BOOL ZombieInit() { |
331 static BOOL initialized = NO; | 253 static BOOL initialized = NO; |
332 if (initialized) | 254 if (initialized) |
333 return YES; | 255 return YES; |
334 | 256 |
335 // Whitelist releases that are compatible with objc zombies. | |
336 if (base::mac::IsOSLeopard()) { | |
337 g_objectDestruct = LookupObjectDestruct_10_5(); | |
338 if (!g_objectDestruct) { | |
339 RecordZombieFailure(FAILED_10_5); | |
340 return NO; | |
341 } | |
342 } else if (base::mac::IsOSSnowLeopard()) { | |
343 g_objectDestruct = LookupObjectDestruct_10_6(); | |
344 if (!g_objectDestruct) { | |
345 RecordZombieFailure(FAILED_10_6); | |
346 return NO; | |
347 } | |
348 } else if (base::mac::IsOSLionOrLater()) { | |
349 // Assume the future looks like the present. | |
350 g_objectDestruct = LookupObjectDestruct_10_6(); | |
351 | |
352 // Put all future failures into the MAX bin. New OS releases come | |
353 // out infrequently enough that this should always correspond to | |
354 // "Next release", and once the next release happens that bin will | |
355 // get an official name. | |
356 if (!g_objectDestruct) { | |
357 RecordZombieFailure(FAILED_MAX); | |
358 return NO; | |
359 } | |
360 } else { | |
361 return NO; | |
362 } | |
363 | |
364 Class rootClass = [NSObject class]; | 257 Class rootClass = [NSObject class]; |
365 g_originalDeallocIMP = | 258 g_originalDeallocIMP = |
366 class_getMethodImplementation(rootClass, @selector(dealloc)); | 259 class_getMethodImplementation(rootClass, @selector(dealloc)); |
367 // objc_getClass() so CrZombie doesn't need +class. | 260 // objc_getClass() so CrZombie doesn't need +class. |
368 g_zombieClass = objc_getClass("CrZombie"); | 261 g_zombieClass = objc_getClass("CrZombie"); |
369 g_fatZombieClass = objc_getClass("CrFatZombie"); | 262 g_fatZombieClass = objc_getClass("CrFatZombie"); |
370 g_fatZombieSize = class_getInstanceSize(g_fatZombieClass); | 263 g_fatZombieSize = class_getInstanceSize(g_fatZombieClass); |
371 | 264 |
372 if (!g_objectDestruct || !g_originalDeallocIMP || | 265 if (!g_originalDeallocIMP || !g_zombieClass || !g_fatZombieClass) |
373 !g_zombieClass || !g_fatZombieClass) | |
374 return NO; | 266 return NO; |
375 | 267 |
376 initialized = YES; | 268 initialized = YES; |
377 return YES; | 269 return YES; |
378 } | 270 } |
379 | 271 |
380 } // namespace | 272 } // namespace |
381 | 273 |
382 @implementation CrZombie | 274 @implementation CrZombie |
383 | 275 |
(...skipping 163 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
547 if (oldZombies) { | 439 if (oldZombies) { |
548 for (size_t i = 0; i < oldCount; ++i) { | 440 for (size_t i = 0; i < oldCount; ++i) { |
549 if (oldZombies[i].object) | 441 if (oldZombies[i].object) |
550 object_dispose(oldZombies[i].object); | 442 object_dispose(oldZombies[i].object); |
551 } | 443 } |
552 free(oldZombies); | 444 free(oldZombies); |
553 } | 445 } |
554 } | 446 } |
555 | 447 |
556 } // namespace ObjcEvilDoers | 448 } // namespace ObjcEvilDoers |
OLD | NEW |