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

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

Issue 7084017: [Mac] Use object_destructInstance() if available. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Push runtime version check down into ZombieInit(). Created 9 years, 6 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
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 <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
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
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
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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698