Home | History | Annotate | Download | only in mac
      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
      3 // found in the LICENSE file.
      4 
      5 #import "chrome/common/mac/objc_zombie.h"
      6 
      7 #include <AvailabilityMacros.h>
      8 
      9 #include <execinfo.h>
     10 #import <objc/runtime.h>
     11 
     12 #include <algorithm>
     13 
     14 #include "base/debug/crash_logging.h"
     15 #include "base/debug/stack_trace.h"
     16 #include "base/lazy_instance.h"
     17 #include "base/logging.h"
     18 #include "base/strings/stringprintf.h"
     19 #include "base/synchronization/lock.h"
     20 #include "chrome/common/crash_keys.h"
     21 #import "chrome/common/mac/objc_method_swizzle.h"
     22 
     23 #if !defined(OS_IOS) && (MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_6)
     24 // Apparently objc/runtime.h doesn't define this with the 10.6 SDK yet.
     25 // The docs say it exists since 10.6 however.
     26 OBJC_EXPORT void *objc_destructInstance(id obj);
     27 #endif
     28 
     29 // Deallocated objects are re-classed as |CrZombie|.  No superclass
     30 // because then the class would have to override many/most of the
     31 // inherited methods (|NSObject| is like a category magnet!).
     32 // Without the __attribute__, clang's -Wobjc-root-class warns on the missing
     33 // superclass.
     34 __attribute__((objc_root_class))
     35 @interface CrZombie  {
     36   Class isa;
     37 }
     38 @end
     39 
     40 // Objects with enough space are made into "fat" zombies, which
     41 // directly remember which class they were until reallocated.
     42 @interface CrFatZombie : CrZombie {
     43  @public
     44   Class wasa;
     45 }
     46 @end
     47 
     48 namespace {
     49 
     50 // The depth of backtrace to store with zombies.  This directly influences
     51 // the amount of memory required to track zombies, so should be kept as
     52 // small as is useful.  Unfortunately, too small and it won't poke through
     53 // deep autorelease and event loop stacks.
     54 // NOTE(shess): Breakpad currently restricts values to 255 bytes.  The
     55 // trace is hex-encoded with "0x" prefix and " " separators, meaning
     56 // the maximum number of 32-bit items which can be encoded is 23.
     57 const size_t kBacktraceDepth = 20;
     58 
     59 // The original implementation for |-[NSObject dealloc]|.
     60 IMP g_originalDeallocIMP = NULL;
     61 
     62 // Classes which freed objects become.  |g_fatZombieSize| is the
     63 // minimum object size which can be made into a fat zombie (which can
     64 // remember which class it was before free, even after falling off the
     65 // treadmill).
     66 Class g_zombieClass = Nil;  // cached [CrZombie class]
     67 Class g_fatZombieClass = Nil;  // cached [CrFatZombie class]
     68 size_t g_fatZombieSize = 0;
     69 
     70 // Whether to zombie all freed objects, or only those which return YES
     71 // from |-shouldBecomeCrZombie|.
     72 BOOL g_zombieAllObjects = NO;
     73 
     74 // Protects |g_zombieCount|, |g_zombieIndex|, and |g_zombies|.
     75 base::LazyInstance<base::Lock>::Leaky g_lock = LAZY_INSTANCE_INITIALIZER;
     76 
     77 // How many zombies to keep before freeing, and the current head of
     78 // the circular buffer.
     79 size_t g_zombieCount = 0;
     80 size_t g_zombieIndex = 0;
     81 
     82 typedef struct {
     83   id object;   // The zombied object.
     84   Class wasa;  // Value of |object->isa| before we replaced it.
     85   void* trace[kBacktraceDepth];  // Backtrace at point of deallocation.
     86   size_t traceDepth;             // Actual depth of trace[].
     87 } ZombieRecord;
     88 
     89 ZombieRecord* g_zombies = NULL;
     90 
     91 // Replacement |-dealloc| which turns objects into zombies and places
     92 // them into |g_zombies| to be freed later.
     93 void ZombieDealloc(id self, SEL _cmd) {
     94   // This code should only be called when it is implementing |-dealloc|.
     95   DCHECK_EQ(_cmd, @selector(dealloc));
     96 
     97   // Use the original |-dealloc| if the object doesn't wish to be
     98   // zombied.
     99   if (!g_zombieAllObjects && ![self shouldBecomeCrZombie]) {
    100     g_originalDeallocIMP(self, _cmd);
    101     return;
    102   }
    103 
    104   Class wasa = object_getClass(self);
    105   const size_t size = class_getInstanceSize(wasa);
    106 
    107   // Destroy the instance by calling C++ destructors and clearing it
    108   // to something unlikely to work well if someone references it.
    109   // NOTE(shess): |object_dispose()| will call this again when the
    110   // zombie falls off the treadmill!  But by then |isa| will be a
    111   // class without C++ destructors or associative references, so it
    112   // won't hurt anything.
    113   objc_destructInstance(self);
    114   memset(self, '!', size);
    115 
    116   // If the instance is big enough, make it into a fat zombie and have
    117   // it remember the old |isa|.  Otherwise make it a regular zombie.
    118   // Setting |isa| rather than using |object_setClass()| because that
    119   // function is implemented with a memory barrier.  The runtime's
    120   // |_internal_object_dispose()| (in objc-class.m) does this, so it
    121   // should be safe (messaging free'd objects shouldn't be expected to
    122   // be thread-safe in the first place).
    123 #pragma clang diagnostic push  // clang warns about direct access to isa.
    124 #pragma clang diagnostic ignored "-Wdeprecated-objc-isa-usage"
    125   if (size >= g_fatZombieSize) {
    126     self->isa = g_fatZombieClass;
    127     static_cast<CrFatZombie*>(self)->wasa = wasa;
    128   } else {
    129     self->isa = g_zombieClass;
    130   }
    131 #pragma clang diagnostic pop
    132 
    133   // The new record to swap into |g_zombies|.  If |g_zombieCount| is
    134   // zero, then |self| will be freed immediately.
    135   ZombieRecord zombieToFree = {self, wasa};
    136   zombieToFree.traceDepth =
    137       std::max(backtrace(zombieToFree.trace, kBacktraceDepth), 0);
    138 
    139   // Don't involve the lock when creating zombies without a treadmill.
    140   if (g_zombieCount > 0) {
    141     base::AutoLock pin(g_lock.Get());
    142 
    143     // Check the count again in a thread-safe manner.
    144     if (g_zombieCount > 0) {
    145       // Put the current object on the treadmill and keep the previous
    146       // occupant.
    147       std::swap(zombieToFree, g_zombies[g_zombieIndex]);
    148 
    149       // Bump the index forward.
    150       g_zombieIndex = (g_zombieIndex + 1) % g_zombieCount;
    151     }
    152   }
    153 
    154   // Do the free out here to prevent any chance of deadlock.
    155   if (zombieToFree.object)
    156     object_dispose(zombieToFree.object);
    157 }
    158 
    159 // Search the treadmill for |object| and fill in |*record| if found.
    160 // Returns YES if found.
    161 BOOL GetZombieRecord(id object, ZombieRecord* record) {
    162   // Holding the lock is reasonable because this should be fast, and
    163   // the process is going to crash presently anyhow.
    164   base::AutoLock pin(g_lock.Get());
    165   for (size_t i = 0; i < g_zombieCount; ++i) {
    166     if (g_zombies[i].object == object) {
    167       *record = g_zombies[i];
    168       return YES;
    169     }
    170   }
    171   return NO;
    172 }
    173 
    174 // Dump the symbols.  This is pulled out into a function to make it
    175 // easy to use DCHECK to dump only in debug builds.
    176 BOOL DumpDeallocTrace(const void* const* array, int size) {
    177   fprintf(stderr, "Backtrace from -dealloc:\n");
    178   base::debug::StackTrace(array, size).Print();
    179 
    180   return YES;
    181 }
    182 
    183 // Log a message to a freed object.  |wasa| is the object's original
    184 // class.  |aSelector| is the selector which the calling code was
    185 // attempting to send.  |viaSelector| is the selector of the
    186 // dispatch-related method which is being invoked to send |aSelector|
    187 // (for instance, -respondsToSelector:).
    188 void ZombieObjectCrash(id object, SEL aSelector, SEL viaSelector) {
    189   ZombieRecord record;
    190   BOOL found = GetZombieRecord(object, &record);
    191 
    192   // The object's class can be in the zombie record, but if that is
    193   // not available it can also be in the object itself (in most cases).
    194   Class wasa = Nil;
    195   if (found) {
    196     wasa = record.wasa;
    197   } else if (object_getClass(object) == g_fatZombieClass) {
    198     wasa = static_cast<CrFatZombie*>(object)->wasa;
    199   }
    200   const char* wasaName = (wasa ? class_getName(wasa) : "<unknown>");
    201 
    202   std::string aString = base::StringPrintf("Zombie <%s: %p> received -%s",
    203       wasaName, object, sel_getName(aSelector));
    204   if (viaSelector != NULL) {
    205     const char* viaName = sel_getName(viaSelector);
    206     base::StringAppendF(&aString, " (via -%s)", viaName);
    207   }
    208 
    209   // Set a value for breakpad to report.
    210   base::debug::SetCrashKeyValue(crash_keys::mac::kZombie, aString);
    211 
    212   // Encode trace into a breakpad key.
    213   if (found) {
    214     base::debug::SetCrashKeyFromAddresses(
    215         crash_keys::mac::kZombieTrace, record.trace, record.traceDepth);
    216   }
    217 
    218   // Log -dealloc backtrace in debug builds then crash with a useful
    219   // stack trace.
    220   if (found && record.traceDepth) {
    221     DCHECK(DumpDeallocTrace(record.trace, record.traceDepth));
    222   } else {
    223     DLOG(INFO) << "Unable to generate backtrace from -dealloc.";
    224   }
    225   DLOG(FATAL) << aString;
    226 
    227   // This is how about:crash is implemented.  Using instead of
    228   // |base::debug::BreakDebugger()| or |LOG(FATAL)| to make the top of
    229   // stack more immediately obvious in crash dumps.
    230   int* zero = NULL;
    231   *zero = 0;
    232 }
    233 
    234 // Initialize our globals, returning YES on success.
    235 BOOL ZombieInit() {
    236   static BOOL initialized = NO;
    237   if (initialized)
    238     return YES;
    239 
    240   Class rootClass = [NSObject class];
    241   g_originalDeallocIMP =
    242       class_getMethodImplementation(rootClass, @selector(dealloc));
    243   // objc_getClass() so CrZombie doesn't need +class.
    244   g_zombieClass = objc_getClass("CrZombie");
    245   g_fatZombieClass = objc_getClass("CrFatZombie");
    246   g_fatZombieSize = class_getInstanceSize(g_fatZombieClass);
    247 
    248   if (!g_originalDeallocIMP || !g_zombieClass || !g_fatZombieClass)
    249     return NO;
    250 
    251   initialized = YES;
    252   return YES;
    253 }
    254 
    255 }  // namespace
    256 
    257 @implementation CrZombie
    258 
    259 // The Objective-C runtime needs to be able to call this successfully.
    260 + (void)initialize {
    261 }
    262 
    263 // Any method not explicitly defined will end up here, forcing a
    264 // crash.
    265 - (id)forwardingTargetForSelector:(SEL)aSelector {
    266   ZombieObjectCrash(self, aSelector, NULL);
    267   return nil;
    268 }
    269 
    270 // Override a few methods often used for dynamic dispatch to log the
    271 // message the caller is attempting to send, rather than the utility
    272 // method being used to send it.
    273 - (BOOL)respondsToSelector:(SEL)aSelector {
    274   ZombieObjectCrash(self, aSelector, _cmd);
    275   return NO;
    276 }
    277 
    278 - (id)performSelector:(SEL)aSelector {
    279   ZombieObjectCrash(self, aSelector, _cmd);
    280   return nil;
    281 }
    282 
    283 - (id)performSelector:(SEL)aSelector withObject:(id)anObject {
    284   ZombieObjectCrash(self, aSelector, _cmd);
    285   return nil;
    286 }
    287 
    288 - (id)performSelector:(SEL)aSelector
    289            withObject:(id)anObject
    290            withObject:(id)anotherObject {
    291   ZombieObjectCrash(self, aSelector, _cmd);
    292   return nil;
    293 }
    294 
    295 - (void)performSelector:(SEL)aSelector
    296              withObject:(id)anArgument
    297              afterDelay:(NSTimeInterval)delay {
    298   ZombieObjectCrash(self, aSelector, _cmd);
    299 }
    300 
    301 @end
    302 
    303 @implementation CrFatZombie
    304 
    305 // This implementation intentionally left empty.
    306 
    307 @end
    308 
    309 @implementation NSObject (CrZombie)
    310 
    311 - (BOOL)shouldBecomeCrZombie {
    312   return NO;
    313 }
    314 
    315 @end
    316 
    317 namespace ObjcEvilDoers {
    318 
    319 bool ZombieEnable(bool zombieAllObjects,
    320                   size_t zombieCount) {
    321   // Only allow enable/disable on the main thread, just to keep things
    322   // simple.
    323   DCHECK([NSThread isMainThread]);
    324 
    325   if (!ZombieInit())
    326     return false;
    327 
    328   g_zombieAllObjects = zombieAllObjects;
    329 
    330   // Replace the implementation of -[NSObject dealloc].
    331   Method m = class_getInstanceMethod([NSObject class], @selector(dealloc));
    332   if (!m)
    333     return false;
    334 
    335   const IMP prevDeallocIMP = method_setImplementation(m, (IMP)ZombieDealloc);
    336   DCHECK(prevDeallocIMP == g_originalDeallocIMP ||
    337          prevDeallocIMP == (IMP)ZombieDealloc);
    338 
    339   // Grab the current set of zombies.  This is thread-safe because
    340   // only the main thread can change these.
    341   const size_t oldCount = g_zombieCount;
    342   ZombieRecord* oldZombies = g_zombies;
    343 
    344   {
    345     base::AutoLock pin(g_lock.Get());
    346 
    347     // Save the old index in case zombies need to be transferred.
    348     size_t oldIndex = g_zombieIndex;
    349 
    350     // Create the new zombie treadmill, disabling zombies in case of
    351     // failure.
    352     g_zombieIndex = 0;
    353     g_zombieCount = zombieCount;
    354     g_zombies = NULL;
    355     if (g_zombieCount) {
    356       g_zombies =
    357           static_cast<ZombieRecord*>(calloc(g_zombieCount, sizeof(*g_zombies)));
    358       if (!g_zombies) {
    359         NOTREACHED();
    360         g_zombies = oldZombies;
    361         g_zombieCount = oldCount;
    362         g_zombieIndex = oldIndex;
    363         ZombieDisable();
    364         return false;
    365       }
    366     }
    367 
    368     // If the count is changing, allow some of the zombies to continue
    369     // shambling forward.
    370     const size_t sharedCount = std::min(oldCount, zombieCount);
    371     if (sharedCount) {
    372       // Get index of the first shared zombie.
    373       oldIndex = (oldIndex + oldCount - sharedCount) % oldCount;
    374 
    375       for (; g_zombieIndex < sharedCount; ++ g_zombieIndex) {
    376         DCHECK_LT(g_zombieIndex, g_zombieCount);
    377         DCHECK_LT(oldIndex, oldCount);
    378         std::swap(g_zombies[g_zombieIndex], oldZombies[oldIndex]);
    379         oldIndex = (oldIndex + 1) % oldCount;
    380       }
    381       g_zombieIndex %= g_zombieCount;
    382     }
    383   }
    384 
    385   // Free the old treadmill and any remaining zombies.
    386   if (oldZombies) {
    387     for (size_t i = 0; i < oldCount; ++i) {
    388       if (oldZombies[i].object)
    389         object_dispose(oldZombies[i].object);
    390     }
    391     free(oldZombies);
    392   }
    393 
    394   return true;
    395 }
    396 
    397 void ZombieDisable() {
    398   // Only allow enable/disable on the main thread, just to keep things
    399   // simple.
    400   DCHECK([NSThread isMainThread]);
    401 
    402   // |ZombieInit()| was never called.
    403   if (!g_originalDeallocIMP)
    404     return;
    405 
    406   // Put back the original implementation of -[NSObject dealloc].
    407   Method m = class_getInstanceMethod([NSObject class], @selector(dealloc));
    408   DCHECK(m);
    409   method_setImplementation(m, g_originalDeallocIMP);
    410 
    411   // Can safely grab this because it only happens on the main thread.
    412   const size_t oldCount = g_zombieCount;
    413   ZombieRecord* oldZombies = g_zombies;
    414 
    415   {
    416     base::AutoLock pin(g_lock.Get());  // In case any -dealloc are in progress.
    417     g_zombieCount = 0;
    418     g_zombies = NULL;
    419   }
    420 
    421   // Free any remaining zombies.
    422   if (oldZombies) {
    423     for (size_t i = 0; i < oldCount; ++i) {
    424       if (oldZombies[i].object)
    425         object_dispose(oldZombies[i].object);
    426     }
    427     free(oldZombies);
    428   }
    429 }
    430 
    431 }  // namespace ObjcEvilDoers
    432