| Index: chrome/browser/ui/cocoa/objc_zombie.mm
|
| diff --git a/chrome/browser/ui/cocoa/objc_zombie.mm b/chrome/browser/ui/cocoa/objc_zombie.mm
|
| index 7dcbfc6b6b6bb79d6ebd63b65b782fe3bf860507..d90be0702ca1ccdf8512b3a51fc9ea99f9c02a0a 100644
|
| --- a/chrome/browser/ui/cocoa/objc_zombie.mm
|
| +++ b/chrome/browser/ui/cocoa/objc_zombie.mm
|
| @@ -11,7 +11,9 @@
|
| #import <objc/objc-class.h>
|
|
|
| #include "base/logging.h"
|
| +#include "base/metrics/histogram.h"
|
| #include "base/synchronization/lock.h"
|
| +#include "base/sys_info.h"
|
| #import "chrome/app/breakpad_mac.h"
|
| #import "chrome/browser/ui/cocoa/objc_method_swizzle.h"
|
|
|
| @@ -33,12 +35,15 @@
|
|
|
| namespace {
|
|
|
| -// |object_cxxDestruct()| is an Objective-C runtime function which
|
| -// traverses the object's class tree for ".cxxdestruct" methods which
|
| -// are run to call C++ destructors as part of |-dealloc|. The
|
| -// function is not public, so must be looked up using nlist.
|
| +// Function which destroys the contents of an object without freeing
|
| +// the object. On 10.5 this is |object_cxxDestruct()|, which
|
| +// traverses the class hierarchy to run the C++ destructors. On 10.6
|
| +// this is |objc_destructInstance()| which calls
|
| +// |object_cxxDestruct()| and removes associative references.
|
| +// |objc_destructInstance()| returns |void*| but pretending it has no
|
| +// return value makes the code simpler.
|
| typedef void DestructFn(id obj);
|
| -DestructFn* g_object_cxxDestruct = NULL;
|
| +DestructFn* g_objectDestruct = NULL;
|
|
|
| // The original implementation for |-[NSObject dealloc]|.
|
| IMP g_originalDeallocIMP = NULL;
|
| @@ -70,30 +75,69 @@ typedef struct {
|
|
|
| ZombieRecord* g_zombies = NULL;
|
|
|
| -// Lookup the private |object_cxxDestruct| function and return a
|
| -// pointer to it. Returns |NULL| on failure.
|
| -DestructFn* LookupObjectCxxDestruct() {
|
| -#if ARCH_CPU_64_BITS
|
| - // TODO(shess): Port to 64-bit. I believe using struct nlist_64
|
| - // will suffice. http://crbug.com/44021 .
|
| - NOTIMPLEMENTED();
|
| +const char* LookupObjcRuntimePath() {
|
| + const void* addr = reinterpret_cast<void*>(&object_getClass);
|
| + Dl_info info;
|
| +
|
| + // |dladdr()| doesn't document how long |info| will stay valid...
|
| + if (dladdr(addr, &info))
|
| + return info.dli_fname;
|
| +
|
| return NULL;
|
| -#endif
|
| +}
|
| +
|
| +// Lookup |objc_destructInstance()| dynamically because it isn't
|
| +// available on 10.5, but we link with the 10.5 SDK.
|
| +DestructFn* LookupObjectDestruct_10_6() {
|
| + const char* objc_runtime_path = LookupObjcRuntimePath();
|
| + if (!objc_runtime_path)
|
| + return NULL;
|
| +
|
| + void* handle = dlopen(objc_runtime_path, RTLD_LAZY | RTLD_LOCAL);
|
| + if (!handle)
|
| + return NULL;
|
| +
|
| + void* fn = dlsym(handle, "objc_destructInstance");
|
| +
|
| + // |fn| would normally be expected to become invalid after this
|
| + // |dlclose()|, but since the |dlopen()| was on a library
|
| + // containing an already-mapped symbol, it will remain valid.
|
| + dlclose(handle);
|
| + return reinterpret_cast<DestructFn*>(fn);
|
| +}
|
| +
|
| +// Under 10.5 |object_cxxDestruct()| is used but unfortunately it is
|
| +// |__private_extern__| in the runtime, meaning |dlsym()| cannot reach it.
|
| +DestructFn* LookupObjectDestruct_10_5() {
|
| + // |nlist()| is only present for 32-bit.
|
| +#if ARCH_CPU_32_BITS
|
| + const char* objc_runtime_path = LookupObjcRuntimePath();
|
| + if (!objc_runtime_path)
|
| + return NULL;
|
|
|
| struct nlist nl[3];
|
| bzero(&nl, sizeof(nl));
|
|
|
| - nl[0].n_un.n_name = (char*)"_object_cxxDestruct";
|
| + nl[0].n_un.n_name = const_cast<char*>("_object_cxxDestruct");
|
|
|
| // My ability to calculate the base for offsets is apparently poor.
|
| // Use |class_addIvar| as a known reference point.
|
| - nl[1].n_un.n_name = (char*)"_class_addIvar";
|
| + nl[1].n_un.n_name = const_cast<char*>("_class_addIvar");
|
|
|
| - if (nlist("/usr/lib/libobjc.dylib", nl) < 0 ||
|
| + if (nlist(objc_runtime_path, nl) < 0 ||
|
| nl[0].n_type == N_UNDF || nl[1].n_type == N_UNDF)
|
| return NULL;
|
|
|
| - return (DestructFn*)((char*)&class_addIvar - nl[1].n_value + nl[0].n_value);
|
| + // Back out offset to |class_addIvar()| to get the baseline, then
|
| + // add back offset to |object_cxxDestruct()|.
|
| + uintptr_t reference_addr = reinterpret_cast<uintptr_t>(&class_addIvar);
|
| + reference_addr -= nl[1].n_value;
|
| + reference_addr += nl[0].n_value;
|
| +
|
| + return reinterpret_cast<DestructFn*>(reference_addr);
|
| +#endif
|
| +
|
| + return NULL;
|
| }
|
|
|
| // Replacement |-dealloc| which turns objects into zombies and places
|
| @@ -109,12 +153,12 @@ void ZombieDealloc(id self, SEL _cmd) {
|
| return;
|
| }
|
|
|
| - // Use the original |-dealloc| if |object_cxxDestruct| was never
|
| + // Use the original |-dealloc| if |g_objectDestruct| was never
|
| // initialized, because otherwise C++ destructors won't be called.
|
| // This case should be impossible, but doing it wrong would cause
|
| // terrible problems.
|
| - DCHECK(g_object_cxxDestruct);
|
| - if (!g_object_cxxDestruct) {
|
| + DCHECK(g_objectDestruct);
|
| + if (!g_objectDestruct) {
|
| g_originalDeallocIMP(self, _cmd);
|
| return;
|
| }
|
| @@ -124,10 +168,11 @@ void ZombieDealloc(id self, SEL _cmd) {
|
|
|
| // Destroy the instance by calling C++ destructors and clearing it
|
| // to something unlikely to work well if someone references it.
|
| - // NOTE(shess): |object_dispose()| will call |object_cxxDestruct()|
|
| - // again when the zombie falls off the treadmill! But by then |isa|
|
| - // will be something without destructors, so it won't hurt anything.
|
| - (*g_object_cxxDestruct)(self);
|
| + // NOTE(shess): |object_dispose()| will call this again when the
|
| + // zombie falls off the treadmill! But by then |isa| will be a
|
| + // class without C++ destructors or associative references, so it
|
| + // won't hurt anything.
|
| + (*g_objectDestruct)(self);
|
| memset(self, '!', size);
|
|
|
| // If the instance is big enough, make it into a fat zombie and have
|
| @@ -214,15 +259,59 @@ void ZombieObjectCrash(id object, SEL aSelector, SEL viaSelector) {
|
| *zero = 0;
|
| }
|
|
|
| +// For monitoring failures in |ZombieInit()|.
|
| +enum ZombieFailure {
|
| + FAILED_10_5,
|
| + FAILED_10_6,
|
| +
|
| + // Add new versions before here.
|
| + FAILED_MAX,
|
| +};
|
| +
|
| +void RecordZombieFailure(ZombieFailure failure) {
|
| + UMA_HISTOGRAM_ENUMERATION("OSX.ZombieInitFailure", failure, FAILED_MAX);
|
| +}
|
| +
|
| // Initialize our globals, returning YES on success.
|
| BOOL ZombieInit() {
|
| static BOOL initialized = NO;
|
| if (initialized)
|
| return YES;
|
|
|
| - Class rootClass = [NSObject class];
|
| + // Whitelist releases that are compatible with objc zombies.
|
| + int32 major_version = 0, minor_version = 0, bugfix_version = 0;
|
| + base::SysInfo::OperatingSystemVersionNumbers(
|
| + &major_version, &minor_version, &bugfix_version);
|
|
|
| - g_object_cxxDestruct = LookupObjectCxxDestruct();
|
| + if (major_version < 10 || (major_version == 10 && minor_version < 5)) {
|
| + return NO;
|
| + } else if (major_version == 10 && minor_version == 5) {
|
| + g_objectDestruct = LookupObjectDestruct_10_5();
|
| + if (!g_objectDestruct) {
|
| + RecordZombieFailure(FAILED_10_5);
|
| + return NO;
|
| + }
|
| + } else if (major_version == 10 && minor_version == 6) {
|
| + g_objectDestruct = LookupObjectDestruct_10_6();
|
| + if (!g_objectDestruct) {
|
| + RecordZombieFailure(FAILED_10_6);
|
| + return NO;
|
| + }
|
| + } else {
|
| + // Assume the future looks like the present.
|
| + g_objectDestruct = LookupObjectDestruct_10_6();
|
| +
|
| + // Put all future failures into the MAX bin. New OS releases come
|
| + // out infrequently enough that this should always correspond to
|
| + // "Next release", and once the next release happens that bin will
|
| + // get an official name.
|
| + if (!g_objectDestruct) {
|
| + RecordZombieFailure(FAILED_MAX);
|
| + return NO;
|
| + }
|
| + }
|
| +
|
| + Class rootClass = [NSObject class];
|
| g_originalDeallocIMP =
|
| class_getMethodImplementation(rootClass, @selector(dealloc));
|
| // objc_getClass() so CrZombie doesn't need +class.
|
| @@ -230,7 +319,7 @@ BOOL ZombieInit() {
|
| g_fatZombieClass = objc_getClass("CrFatZombie");
|
| g_fatZombieSize = class_getInstanceSize(g_fatZombieClass);
|
|
|
| - if (!g_object_cxxDestruct || !g_originalDeallocIMP ||
|
| + if (!g_objectDestruct || !g_originalDeallocIMP ||
|
| !g_zombieClass || !g_fatZombieClass)
|
| return NO;
|
|
|
|
|