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 <mach-o/dyld.h> | 8 #include <mach-o/dyld.h> |
9 #include <mach-o/nlist.h> | 9 #include <mach-o/nlist.h> |
10 | 10 |
11 #import <objc/objc-class.h> | 11 #import <objc/objc-class.h> |
12 | 12 |
13 #include "base/logging.h" | 13 #include "base/logging.h" |
14 #include "base/metrics/histogram.h" | |
14 #include "base/synchronization/lock.h" | 15 #include "base/synchronization/lock.h" |
16 #include "base/sys_info.h" | |
15 #import "chrome/app/breakpad_mac.h" | 17 #import "chrome/app/breakpad_mac.h" |
16 #import "chrome/browser/ui/cocoa/objc_method_swizzle.h" | 18 #import "chrome/browser/ui/cocoa/objc_method_swizzle.h" |
17 | 19 |
18 // Deallocated objects are re-classed as |CrZombie|. No superclass | 20 // Deallocated objects are re-classed as |CrZombie|. No superclass |
19 // because then the class would have to override many/most of the | 21 // because then the class would have to override many/most of the |
20 // inherited methods (|NSObject| is like a category magnet!). | 22 // inherited methods (|NSObject| is like a category magnet!). |
21 @interface CrZombie { | 23 @interface CrZombie { |
22 Class isa; | 24 Class isa; |
23 } | 25 } |
24 @end | 26 @end |
25 | 27 |
26 // Objects with enough space are made into "fat" zombies, which | 28 // Objects with enough space are made into "fat" zombies, which |
27 // directly remember which class they were until reallocated. | 29 // directly remember which class they were until reallocated. |
28 @interface CrFatZombie : CrZombie { | 30 @interface CrFatZombie : CrZombie { |
29 @public | 31 @public |
30 Class wasa; | 32 Class wasa; |
31 } | 33 } |
32 @end | 34 @end |
33 | 35 |
34 namespace { | 36 namespace { |
35 | 37 |
36 // |object_cxxDestruct()| is an Objective-C runtime function which | 38 // Function which destroys the contents of an object without freeing |
37 // traverses the object's class tree for ".cxxdestruct" methods which | 39 // the object. On 10.5 this is |object_cxxDestruct()|, which |
38 // are run to call C++ destructors as part of |-dealloc|. The | 40 // traverses the class hierarchy to run the C++ destructors. On 10.6 |
39 // function is not public, so must be looked up using nlist. | 41 // this is |objc_destructInstance()| which calls |
42 // |object_cxxDestruct()| and removes associative references. | |
43 // |objc_destructInstance()| returns |void*|, pretending it has no | |
Avi (use Gerrit)
2011/06/08 03:08:53
"... returns |void*| but pretending..."
| |
44 // return value makes the code simpler. | |
40 typedef void DestructFn(id obj); | 45 typedef void DestructFn(id obj); |
41 DestructFn* g_object_cxxDestruct = NULL; | 46 DestructFn* g_objectDestruct = NULL; |
42 | 47 |
43 // The original implementation for |-[NSObject dealloc]|. | 48 // The original implementation for |-[NSObject dealloc]|. |
44 IMP g_originalDeallocIMP = NULL; | 49 IMP g_originalDeallocIMP = NULL; |
45 | 50 |
46 // Classes which freed objects become. |g_fatZombieSize| is the | 51 // Classes which freed objects become. |g_fatZombieSize| is the |
47 // minimum object size which can be made into a fat zombie (which can | 52 // minimum object size which can be made into a fat zombie (which can |
48 // remember which class it was before free, even after falling off the | 53 // remember which class it was before free, even after falling off the |
49 // treadmill). | 54 // treadmill). |
50 Class g_zombieClass = Nil; // cached [CrZombie class] | 55 Class g_zombieClass = Nil; // cached [CrZombie class] |
51 Class g_fatZombieClass = Nil; // cached [CrFatZombie class] | 56 Class g_fatZombieClass = Nil; // cached [CrFatZombie class] |
(...skipping 11 matching lines...) Expand all Loading... | |
63 size_t g_zombieCount = 0; | 68 size_t g_zombieCount = 0; |
64 size_t g_zombieIndex = 0; | 69 size_t g_zombieIndex = 0; |
65 | 70 |
66 typedef struct { | 71 typedef struct { |
67 id object; // The zombied object. | 72 id object; // The zombied object. |
68 Class wasa; // Value of |object->isa| before we replaced it. | 73 Class wasa; // Value of |object->isa| before we replaced it. |
69 } ZombieRecord; | 74 } ZombieRecord; |
70 | 75 |
71 ZombieRecord* g_zombies = NULL; | 76 ZombieRecord* g_zombies = NULL; |
72 | 77 |
73 // Lookup the private |object_cxxDestruct| function and return a | 78 const char* LookupObjcRuntimePath() { |
74 // pointer to it. Returns |NULL| on failure. | 79 const void* addr = reinterpret_cast<void*>(&object_getClass); |
75 DestructFn* LookupObjectCxxDestruct() { | 80 Dl_info info; |
76 #if ARCH_CPU_64_BITS | 81 |
77 // TODO(shess): Port to 64-bit. I believe using struct nlist_64 | 82 // |dladdr()| doesn't document how long |info| will stay valid... |
78 // will suffice. http://crbug.com/44021 . | 83 if (dladdr(addr, &info)) |
79 NOTIMPLEMENTED(); | 84 return info.dli_fname; |
85 | |
80 return NULL; | 86 return NULL; |
81 #endif | 87 } |
88 | |
89 // Lookup |objc_destructInstance()| dynamically because it isn't | |
90 // available on 10.5, but we link with the 10.5 SDK. | |
91 DestructFn* LookupObjectDestruct_10_6() { | |
92 const char* objc_runtime_path = LookupObjcRuntimePath(); | |
93 if (!objc_runtime_path) | |
94 return NULL; | |
95 | |
96 void* handle = dlopen(objc_runtime_path, RTLD_LAZY | RTLD_LOCAL); | |
97 if (!handle) | |
98 return NULL; | |
99 | |
100 void* fn = dlsym(handle, "objc_destructInstance"); | |
101 | |
102 // |fn| would normally be expected to become invalid after this | |
103 // |dlclose()|, but since the |dlopen()| was on a library | |
104 // containing an already-mapped symbol, it will remain valid. | |
105 dlclose(handle); | |
106 return reinterpret_cast<DestructFn*>(fn); | |
107 } | |
108 | |
109 // Under 10.5 |object_cxxDestruct()| is used, unfortunately it is | |
Avi (use Gerrit)
2011/06/08 03:08:53
"used but unfortunately"
| |
110 // |__private_extern__| in the runtime, meaning |dlsym()| cannot reach it. | |
111 DestructFn* LookupObjectDestruct_10_5() { | |
112 // |nlist()| is only present for 32-bit. | |
113 #if ARCH_CPU_32_BITS | |
114 const char* objc_runtime_path = LookupObjcRuntimePath(); | |
115 if (!objc_runtime_path) | |
116 return NULL; | |
82 | 117 |
83 struct nlist nl[3]; | 118 struct nlist nl[3]; |
84 bzero(&nl, sizeof(nl)); | 119 bzero(&nl, sizeof(nl)); |
85 | 120 |
86 nl[0].n_un.n_name = (char*)"_object_cxxDestruct"; | 121 nl[0].n_un.n_name = const_cast<char*>("_object_cxxDestruct"); |
87 | 122 |
88 // My ability to calculate the base for offsets is apparently poor. | 123 // My ability to calculate the base for offsets is apparently poor. |
89 // Use |class_addIvar| as a known reference point. | 124 // Use |class_addIvar| as a known reference point. |
90 nl[1].n_un.n_name = (char*)"_class_addIvar"; | 125 nl[1].n_un.n_name = const_cast<char*>("_class_addIvar"); |
91 | 126 |
92 if (nlist("/usr/lib/libobjc.dylib", nl) < 0 || | 127 if (nlist(objc_runtime_path, nl) < 0 || |
93 nl[0].n_type == N_UNDF || nl[1].n_type == N_UNDF) | 128 nl[0].n_type == N_UNDF || nl[1].n_type == N_UNDF) |
94 return NULL; | 129 return NULL; |
95 | 130 |
96 return (DestructFn*)((char*)&class_addIvar - nl[1].n_value + nl[0].n_value); | 131 // Back out offset to |class_addIvar()| to get the baseline, then |
132 // add back offset to |object_cxxDestruct()|. | |
133 uintptr_t reference_addr = reinterpret_cast<uintptr_t>(&class_addIvar); | |
134 reference_addr -= nl[1].n_value; | |
135 reference_addr += nl[0].n_value; | |
136 | |
137 return reinterpret_cast<DestructFn*>(reference_addr); | |
138 #endif | |
139 | |
140 return NULL; | |
97 } | 141 } |
98 | 142 |
99 // Replacement |-dealloc| which turns objects into zombies and places | 143 // Replacement |-dealloc| which turns objects into zombies and places |
100 // them into |g_zombies| to be freed later. | 144 // them into |g_zombies| to be freed later. |
101 void ZombieDealloc(id self, SEL _cmd) { | 145 void ZombieDealloc(id self, SEL _cmd) { |
102 // This code should only be called when it is implementing |-dealloc|. | 146 // This code should only be called when it is implementing |-dealloc|. |
103 DCHECK_EQ(_cmd, @selector(dealloc)); | 147 DCHECK_EQ(_cmd, @selector(dealloc)); |
104 | 148 |
105 // Use the original |-dealloc| if the object doesn't wish to be | 149 // Use the original |-dealloc| if the object doesn't wish to be |
106 // zombied. | 150 // zombied. |
107 if (!g_zombieAllObjects && ![self shouldBecomeCrZombie]) { | 151 if (!g_zombieAllObjects && ![self shouldBecomeCrZombie]) { |
108 g_originalDeallocIMP(self, _cmd); | 152 g_originalDeallocIMP(self, _cmd); |
109 return; | 153 return; |
110 } | 154 } |
111 | 155 |
112 // Use the original |-dealloc| if |object_cxxDestruct| was never | 156 // Use the original |-dealloc| if |g_objectDestruct| was never |
113 // initialized, because otherwise C++ destructors won't be called. | 157 // initialized, because otherwise C++ destructors won't be called. |
114 // This case should be impossible, but doing it wrong would cause | 158 // This case should be impossible, but doing it wrong would cause |
115 // terrible problems. | 159 // terrible problems. |
116 DCHECK(g_object_cxxDestruct); | 160 DCHECK(g_objectDestruct); |
117 if (!g_object_cxxDestruct) { | 161 if (!g_objectDestruct) { |
118 g_originalDeallocIMP(self, _cmd); | 162 g_originalDeallocIMP(self, _cmd); |
119 return; | 163 return; |
120 } | 164 } |
121 | 165 |
122 Class wasa = object_getClass(self); | 166 Class wasa = object_getClass(self); |
123 const size_t size = class_getInstanceSize(wasa); | 167 const size_t size = class_getInstanceSize(wasa); |
124 | 168 |
125 // Destroy the instance by calling C++ destructors and clearing it | 169 // Destroy the instance by calling C++ destructors and clearing it |
126 // to something unlikely to work well if someone references it. | 170 // to something unlikely to work well if someone references it. |
127 // NOTE(shess): |object_dispose()| will call |object_cxxDestruct()| | 171 // NOTE(shess): |object_dispose()| will call this again when the |
128 // again when the zombie falls off the treadmill! But by then |isa| | 172 // zombie falls off the treadmill! But by then |isa| will be a |
129 // will be something without destructors, so it won't hurt anything. | 173 // class without C++ destructors or associative references, so it |
130 (*g_object_cxxDestruct)(self); | 174 // won't hurt anything. |
175 (*g_objectDestruct)(self); | |
131 memset(self, '!', size); | 176 memset(self, '!', size); |
132 | 177 |
133 // If the instance is big enough, make it into a fat zombie and have | 178 // If the instance is big enough, make it into a fat zombie and have |
134 // it remember the old |isa|. Otherwise make it a regular zombie. | 179 // it remember the old |isa|. Otherwise make it a regular zombie. |
135 // Setting |isa| rather than using |object_setClass()| because that | 180 // Setting |isa| rather than using |object_setClass()| because that |
136 // function is implemented with a memory barrier. The runtime's | 181 // function is implemented with a memory barrier. The runtime's |
137 // |_internal_object_dispose()| (in objc-class.m) does this, so it | 182 // |_internal_object_dispose()| (in objc-class.m) does this, so it |
138 // should be safe (messaging free'd objects shouldn't be expected to | 183 // should be safe (messaging free'd objects shouldn't be expected to |
139 // be thread-safe in the first place). | 184 // be thread-safe in the first place). |
140 if (size >= g_fatZombieSize) { | 185 if (size >= g_fatZombieSize) { |
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
207 SetCrashKeyValue(@"zombie", aString); | 252 SetCrashKeyValue(@"zombie", aString); |
208 LOG(ERROR) << [aString UTF8String]; | 253 LOG(ERROR) << [aString UTF8String]; |
209 | 254 |
210 // This is how about:crash is implemented. Using instead of | 255 // This is how about:crash is implemented. Using instead of |
211 // |baes::debug::BreakDebugger()| or |LOG(FATAL)| to make the top of | 256 // |baes::debug::BreakDebugger()| or |LOG(FATAL)| to make the top of |
212 // stack more immediately obvious in crash dumps. | 257 // stack more immediately obvious in crash dumps. |
213 int* zero = NULL; | 258 int* zero = NULL; |
214 *zero = 0; | 259 *zero = 0; |
215 } | 260 } |
216 | 261 |
262 // For monitoring failures in |ZombieInit()|. | |
263 enum ZombieFailure { | |
264 FAILED_10_5, | |
265 FAILED_10_6, | |
266 | |
267 // Add new versions before here. | |
268 FAILED_MAX, | |
269 }; | |
270 | |
271 void RecordZombieFailure(ZombieFailure failure) { | |
272 UMA_HISTOGRAM_ENUMERATION("OSX.ZombieInitFailure", failure, FAILED_MAX); | |
273 } | |
274 | |
217 // Initialize our globals, returning YES on success. | 275 // Initialize our globals, returning YES on success. |
218 BOOL ZombieInit() { | 276 BOOL ZombieInit() { |
219 static BOOL initialized = NO; | 277 static BOOL initialized = NO; |
220 if (initialized) | 278 if (initialized) |
221 return YES; | 279 return YES; |
222 | 280 |
281 // Whitelist releases that are compatible with objc zombies. | |
282 int32 major_version = 0, minor_version = 0, bugfix_version = 0; | |
283 base::SysInfo::OperatingSystemVersionNumbers( | |
284 &major_version, &minor_version, &bugfix_version); | |
285 | |
286 if (major_version < 10 || (major_version == 10 && minor_version < 5)) { | |
287 return NO; | |
288 } else if (major_version == 10 && minor_version == 5) { | |
289 g_objectDestruct = LookupObjectDestruct_10_5(); | |
290 if (!g_objectDestruct) { | |
291 RecordZombieFailure(FAILED_10_5); | |
292 return NO; | |
293 } | |
294 } else if (major_version == 10 && minor_version == 6) { | |
295 g_objectDestruct = LookupObjectDestruct_10_6(); | |
296 if (!g_objectDestruct) { | |
297 RecordZombieFailure(FAILED_10_6); | |
298 return NO; | |
299 } | |
300 } else { | |
301 // Assume the future looks like the present. | |
302 g_objectDestruct = LookupObjectDestruct_10_6(); | |
Scott Hess - ex-Googler
2011/06/07 23:51:25
Note that what's currently on trunk disables CrZom
Mark Mentovai
2011/06/08 02:27:48
shess wrote:
| |
303 | |
304 // Put all future failures into the MAX bin. New OS releases come | |
305 // out infrequently enough that this should always correspond to | |
306 // "Next release", and once the next release happens that bin will | |
307 // get an official name. | |
308 if (!g_objectDestruct) { | |
309 RecordZombieFailure(FAILED_MAX); | |
310 return NO; | |
311 } | |
312 } | |
313 | |
223 Class rootClass = [NSObject class]; | 314 Class rootClass = [NSObject class]; |
224 | |
225 g_object_cxxDestruct = LookupObjectCxxDestruct(); | |
226 g_originalDeallocIMP = | 315 g_originalDeallocIMP = |
227 class_getMethodImplementation(rootClass, @selector(dealloc)); | 316 class_getMethodImplementation(rootClass, @selector(dealloc)); |
228 // objc_getClass() so CrZombie doesn't need +class. | 317 // objc_getClass() so CrZombie doesn't need +class. |
229 g_zombieClass = objc_getClass("CrZombie"); | 318 g_zombieClass = objc_getClass("CrZombie"); |
230 g_fatZombieClass = objc_getClass("CrFatZombie"); | 319 g_fatZombieClass = objc_getClass("CrFatZombie"); |
231 g_fatZombieSize = class_getInstanceSize(g_fatZombieClass); | 320 g_fatZombieSize = class_getInstanceSize(g_fatZombieClass); |
232 | 321 |
233 if (!g_object_cxxDestruct || !g_originalDeallocIMP || | 322 if (!g_objectDestruct || !g_originalDeallocIMP || |
234 !g_zombieClass || !g_fatZombieClass) | 323 !g_zombieClass || !g_fatZombieClass) |
235 return NO; | 324 return NO; |
236 | 325 |
237 initialized = YES; | 326 initialized = YES; |
238 return YES; | 327 return YES; |
239 } | 328 } |
240 | 329 |
241 } // namespace | 330 } // namespace |
242 | 331 |
243 @implementation CrZombie | 332 @implementation CrZombie |
(...skipping 164 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
408 if (oldZombies) { | 497 if (oldZombies) { |
409 for (size_t i = 0; i < oldCount; ++i) { | 498 for (size_t i = 0; i < oldCount; ++i) { |
410 if (oldZombies[i].object) | 499 if (oldZombies[i].object) |
411 object_dispose(oldZombies[i].object); | 500 object_dispose(oldZombies[i].object); |
412 } | 501 } |
413 free(oldZombies); | 502 free(oldZombies); |
414 } | 503 } |
415 } | 504 } |
416 | 505 |
417 } // namespace ObjcEvilDoers | 506 } // namespace ObjcEvilDoers |
OLD | NEW |