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/posix/eintr_wrapper.h"
     19 #include "base/strings/stringprintf.h"
     20 #include "base/synchronization/lock.h"
     21 #include "chrome/common/crash_keys.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   // Async-signal safe version of fputs, consistent with StackTrace::Print().
    178   const char* message = "Backtrace from -dealloc:\n";
    179   ignore_result(HANDLE_EINTR(write(STDERR_FILENO, message, strlen(message))));
    180   base::debug::StackTrace(array, size).Print();
    181 
    182   return YES;
    183 }
    184 
    185 // Log a message to a freed object.  |wasa| is the object's original
    186 // class.  |aSelector| is the selector which the calling code was
    187 // attempting to send.  |viaSelector| is the selector of the
    188 // dispatch-related method which is being invoked to send |aSelector|
    189 // (for instance, -respondsToSelector:).
    190 void ZombieObjectCrash(id object, SEL aSelector, SEL viaSelector) {
    191   ZombieRecord record;
    192   BOOL found = GetZombieRecord(object, &record);
    193 
    194   // The object's class can be in the zombie record, but if that is
    195   // not available it can also be in the object itself (in most cases).
    196   Class wasa = Nil;
    197   if (found) {
    198     wasa = record.wasa;
    199   } else if (object_getClass(object) == g_fatZombieClass) {
    200     wasa = static_cast<CrFatZombie*>(object)->wasa;
    201   }
    202   const char* wasaName = (wasa ? class_getName(wasa) : "<unknown>");
    203 
    204   std::string aString = base::StringPrintf("Zombie <%s: %p> received -%s",
    205       wasaName, object, sel_getName(aSelector));
    206   if (viaSelector != NULL) {
    207     const char* viaName = sel_getName(viaSelector);
    208     base::StringAppendF(&aString, " (via -%s)", viaName);
    209   }
    210 
    211   // Set a value for breakpad to report.
    212   base::debug::SetCrashKeyValue(crash_keys::mac::kZombie, aString);
    213 
    214   // Encode trace into a breakpad key.
    215   if (found) {
    216     base::debug::SetCrashKeyFromAddresses(
    217         crash_keys::mac::kZombieTrace, record.trace, record.traceDepth);
    218   }
    219 
    220   // Log -dealloc backtrace in debug builds then crash with a useful
    221   // stack trace.
    222   if (found && record.traceDepth) {
    223     DCHECK(DumpDeallocTrace(record.trace, record.traceDepth));
    224   } else {
    225     DLOG(WARNING) << "Unable to generate backtrace from -dealloc.";
    226   }
    227   DLOG(FATAL) << aString;
    228 
    229   // This is how about:crash is implemented.  Using instead of
    230   // |base::debug::BreakDebugger()| or |LOG(FATAL)| to make the top of
    231   // stack more immediately obvious in crash dumps.
    232   int* zero = NULL;
    233   *zero = 0;
    234 }
    235 
    236 // Initialize our globals, returning YES on success.
    237 BOOL ZombieInit() {
    238   static BOOL initialized = NO;
    239   if (initialized)
    240     return YES;
    241 
    242   Class rootClass = [NSObject class];
    243   g_originalDeallocIMP =
    244       class_getMethodImplementation(rootClass, @selector(dealloc));
    245   // objc_getClass() so CrZombie doesn't need +class.
    246   g_zombieClass = objc_getClass("CrZombie");
    247   g_fatZombieClass = objc_getClass("CrFatZombie");
    248   g_fatZombieSize = class_getInstanceSize(g_fatZombieClass);
    249 
    250   if (!g_originalDeallocIMP || !g_zombieClass || !g_fatZombieClass)
    251     return NO;
    252 
    253   initialized = YES;
    254   return YES;
    255 }
    256 
    257 }  // namespace
    258 
    259 @implementation CrZombie
    260 
    261 // The Objective-C runtime needs to be able to call this successfully.
    262 + (void)initialize {
    263 }
    264 
    265 // Any method not explicitly defined will end up here, forcing a
    266 // crash.
    267 - (id)forwardingTargetForSelector:(SEL)aSelector {
    268   ZombieObjectCrash(self, aSelector, NULL);
    269   return nil;
    270 }
    271 
    272 // Override a few methods often used for dynamic dispatch to log the
    273 // message the caller is attempting to send, rather than the utility
    274 // method being used to send it.
    275 - (BOOL)respondsToSelector:(SEL)aSelector {
    276   ZombieObjectCrash(self, aSelector, _cmd);
    277   return NO;
    278 }
    279 
    280 - (id)performSelector:(SEL)aSelector {
    281   ZombieObjectCrash(self, aSelector, _cmd);
    282   return nil;
    283 }
    284 
    285 - (id)performSelector:(SEL)aSelector withObject:(id)anObject {
    286   ZombieObjectCrash(self, aSelector, _cmd);
    287   return nil;
    288 }
    289 
    290 - (id)performSelector:(SEL)aSelector
    291            withObject:(id)anObject
    292            withObject:(id)anotherObject {
    293   ZombieObjectCrash(self, aSelector, _cmd);
    294   return nil;
    295 }
    296 
    297 - (void)performSelector:(SEL)aSelector
    298              withObject:(id)anArgument
    299              afterDelay:(NSTimeInterval)delay {
    300   ZombieObjectCrash(self, aSelector, _cmd);
    301 }
    302 
    303 @end
    304 
    305 @implementation CrFatZombie
    306 
    307 // This implementation intentionally left empty.
    308 
    309 @end
    310 
    311 @implementation NSObject (CrZombie)
    312 
    313 - (BOOL)shouldBecomeCrZombie {
    314   return NO;
    315 }
    316 
    317 @end
    318 
    319 namespace ObjcEvilDoers {
    320 
    321 bool ZombieEnable(bool zombieAllObjects,
    322                   size_t zombieCount) {
    323   // Only allow enable/disable on the main thread, just to keep things
    324   // simple.
    325   DCHECK([NSThread isMainThread]);
    326 
    327   if (!ZombieInit())
    328     return false;
    329 
    330   g_zombieAllObjects = zombieAllObjects;
    331 
    332   // Replace the implementation of -[NSObject dealloc].
    333   Method m = class_getInstanceMethod([NSObject class], @selector(dealloc));
    334   if (!m)
    335     return false;
    336 
    337   const IMP prevDeallocIMP = method_setImplementation(m, (IMP)ZombieDealloc);
    338   DCHECK(prevDeallocIMP == g_originalDeallocIMP ||
    339          prevDeallocIMP == (IMP)ZombieDealloc);
    340 
    341   // Grab the current set of zombies.  This is thread-safe because
    342   // only the main thread can change these.
    343   const size_t oldCount = g_zombieCount;
    344   ZombieRecord* oldZombies = g_zombies;
    345 
    346   {
    347     base::AutoLock pin(g_lock.Get());
    348 
    349     // Save the old index in case zombies need to be transferred.
    350     size_t oldIndex = g_zombieIndex;
    351 
    352     // Create the new zombie treadmill, disabling zombies in case of
    353     // failure.
    354     g_zombieIndex = 0;
    355     g_zombieCount = zombieCount;
    356     g_zombies = NULL;
    357     if (g_zombieCount) {
    358       g_zombies =
    359           static_cast<ZombieRecord*>(calloc(g_zombieCount, sizeof(*g_zombies)));
    360       if (!g_zombies) {
    361         NOTREACHED();
    362         g_zombies = oldZombies;
    363         g_zombieCount = oldCount;
    364         g_zombieIndex = oldIndex;
    365         ZombieDisable();
    366         return false;
    367       }
    368     }
    369 
    370     // If the count is changing, allow some of the zombies to continue
    371     // shambling forward.
    372     const size_t sharedCount = std::min(oldCount, zombieCount);
    373     if (sharedCount) {
    374       // Get index of the first shared zombie.
    375       oldIndex = (oldIndex + oldCount - sharedCount) % oldCount;
    376 
    377       for (; g_zombieIndex < sharedCount; ++ g_zombieIndex) {
    378         DCHECK_LT(g_zombieIndex, g_zombieCount);
    379         DCHECK_LT(oldIndex, oldCount);
    380         std::swap(g_zombies[g_zombieIndex], oldZombies[oldIndex]);
    381         oldIndex = (oldIndex + 1) % oldCount;
    382       }
    383       g_zombieIndex %= g_zombieCount;
    384     }
    385   }
    386 
    387   // Free the old treadmill and any remaining zombies.
    388   if (oldZombies) {
    389     for (size_t i = 0; i < oldCount; ++i) {
    390       if (oldZombies[i].object)
    391         object_dispose(oldZombies[i].object);
    392     }
    393     free(oldZombies);
    394   }
    395 
    396   return true;
    397 }
    398 
    399 void ZombieDisable() {
    400   // Only allow enable/disable on the main thread, just to keep things
    401   // simple.
    402   DCHECK([NSThread isMainThread]);
    403 
    404   // |ZombieInit()| was never called.
    405   if (!g_originalDeallocIMP)
    406     return;
    407 
    408   // Put back the original implementation of -[NSObject dealloc].
    409   Method m = class_getInstanceMethod([NSObject class], @selector(dealloc));
    410   DCHECK(m);
    411   method_setImplementation(m, g_originalDeallocIMP);
    412 
    413   // Can safely grab this because it only happens on the main thread.
    414   const size_t oldCount = g_zombieCount;
    415   ZombieRecord* oldZombies = g_zombies;
    416 
    417   {
    418     base::AutoLock pin(g_lock.Get());  // In case any -dealloc are in progress.
    419     g_zombieCount = 0;
    420     g_zombies = NULL;
    421   }
    422 
    423   // Free any remaining zombies.
    424   if (oldZombies) {
    425     for (size_t i = 0; i < oldCount; ++i) {
    426       if (oldZombies[i].object)
    427         object_dispose(oldZombies[i].object);
    428     }
    429     free(oldZombies);
    430   }
    431 }
    432 
    433 }  // namespace ObjcEvilDoers
    434