美文网首页
[转]The Inner Life of Zombies

[转]The Inner Life of Zombies

作者: __承_影__ | 来源:发表于2018-07-30 00:30 被阅读0次

Friday Q&A 2011-05-20: The Inner Life of Zombies

byMike Ash

It's Friday again, that Fridayest of days, and this week that means it's time for another Friday Q&A. Samuel Goodwin suggested discussing howNSZombieworks, and that's the topic I will discuss today.

Zombie Overview

As you may recall, an Objective-C object is just a block of allocated memory. The first pointer-sized chunk of that block is theisapointer, which points to the object's class. The rest of the block contains the object's instance variables.

When an object is deallocated, the block of memory which contains it

is freed. Normally this means that it's simply marked as being available

for reuse. If you've screwed up and kept a pointer to this deallocated

object, many mysterious things can happen.

In some cases, code which tries to use the deallocated object will

work just fine. If the deallocated memory hasn't actually been

overwritten yet, it will still behave like a normal Objective-C object.

Frequently, the deallocated memory will be reused to hold a new object. In this case, the old pointer ends up pointing to this new object. Attempts to use the old pointer will send messages to the new object instead, with confusing results. This is why one of the most common symptoms of a memory management error is a mystery object, like a randomNSString, showing up where you expected to see something else.

Occasionally, the memory will be overwritten with something that's

not an object, and your code crashes. This is the best outcome of the

three, since it fails more quickly and makes it more clear what's going

wrong, but it also tends to be rare.

Zombies greatly improve the diagnostics available for this common

scenario. Instead of simply leaving the deallocated memory alone,

zombies take it over and replace it with an object which traps all

attempts to access it. Thus the term "zombie": the dead object is

resurrected to a sort of unlife. When a zombie object is messaged, it

logs an error and crashes, providing a convenient backtrace so you can

see exactly where the problem lies.

Using Zombies

Zombies can be enabled by setting theNSZombieEnabledenvironment variable toYES. Run the app ingdb, and it will crash on any attempted access to a dead object. Be careful, though: by default, zombies are never deallocated, so your app's memory usage can become extremely high.

Another useful option is the Zombies instruments in Instruments. This

enables zombies and also tracks objects' retain counts so that you can

go back and see the retain/release activity for any improperly messaged

object.

Investigating Zombies

Let's take a look at what these things are doing behind the scenes. To help with the investigation, I wrote a small function to dump the contents of an object:

voidDump(NSString*msg,idobj,intsize){NSString*s=[NSStringstringWithFormat:@"%@ malloc_size %d - %@",msg,(int)malloc_size(obj),[NSDatadataWithBytes:objlength:size]];printf("%s\n",[sUTF8String]);}

For the size, the caller can usemalloc_sizeto get the size of the allocated block of memory. This is left up to the caller becausemalloc_sizewon't work on a deallocated block, so the caller will need to fetch the size while the object is still live and then keep it around.

Let's create anNSObjectand log it before and after being destroyed:

idobj=[[NSObjectalloc]init];intsize=malloc_size(obj);Dump(@"Fresh NSObject",obj,size);[objrelease];Dump(@"Destroyed NSObject",obj,size);

Here's a normal run without zombies:

FreshNSObjectmalloc_size16-<68046370ff7f00000000000000000000>DestroyedNSObjectmalloc_size0-<68046370ff7f00000000000000000000>

Notice howmalloc_sizewent to0after being destroyed, indicating that the memory block is now freed. Also notice that nothing else changes. The object contains the exact same thing before and after being destroyed. This object could still be used after being destroyed.

Let's try another one with zombies enabled:

FreshNSObjectmalloc_size16-<68046370ff7f00000000000000000000>DestroyedNSObjectmalloc_size16-

Now we're seeing some differences. First of all,malloc_sizeis still reporting16, so the memory was never deallocated. Secondly, the contents of the object have changed. Theisapointer occupies the first eight bytes of the object (running in 64-bit mode here), or the first two groups in the above dump. Theisapointer is completely different afterwards.

The second eight bytes is just unused here. Let's write a quick dummy class that uses it and see how it behaves:

@interfaceDummy:NSObject{uintptr_tsecondEight;}@end

@implementationDummy-(id)init{if((self=[superinit]))secondEight=0xdeadbeefcafebabeULL;returnself;}@end

Let's then add some code to dump one of these as well:

obj=[[Dummyalloc]init];size=malloc_size(obj);Dump(@"Fresh Dummy",obj,size);[objrelease];Dump(@"Destroyed Dummy",obj,size);

Here's a run without zombies:

FreshDummymalloc_size16-<2811000001000000bebafecaefbeadde>DestroyedDummymalloc_size0-<2811000001000000bebafecaefbeadde>

As before, nothing changes when it's deallocated. Note that the contents ofsecondEightare backwards because this code is running on a little-endian architecture.

Here's a run with zombies:

FreshDummymalloc_size16-<2811000001000000bebafecaefbeadde>DestroyedDummymalloc_size16-

The rest of the object is left alone, but once again theisapointer is overwritten. Let's see just what this newisapointer is:

NSLog(@"%s",class_getName(object_getClass(obj)));

Running this tells us that the class is called_NSZombie_Dummy. We can see that zombies work by overwriting theisapointer with a special zombie class. This special zombie class incorporates the name of the original class, making it easy to see what the original class was and making diagnostics much simpler.

Let's see just what this class contains. Here's a function which will dump out various information about a class:

voidDumpClass(Classc){printf("Dumping class %s\n",class_getName(c));printf("Superclass: %s\n",class_getName(class_getSuperclass(c)));printf("Ivars:\n");Ivar*ivars=class_copyIvarList(c,NULL);for(Ivar*cursor=ivars;cursor&&*cursor;cursor++)printf("    %s %s %d\n",ivar_getName(*cursor),ivar_getTypeEncoding(*cursor),(int)ivar_getOffset(*cursor));free(ivars);printf("Methods:\n");Method*methods=class_copyMethodList(c,NULL);for(Method*cursor=methods;cursor&&*cursor;cursor++)fprintf(stderr,"    %s %s\n",sel_getName(method_getName(*cursor)),method_getTypeEncoding(*cursor));free(methods);}

Now to run this on the deallocated instance ofDummy:

DumpClass(object_getClass(obj));

Here's what it prints:

    Dumping class _NSZombie_Dummy

    Superclass: nil

    Ivars:

        isa # 0

    Methods:

This class contains essentially nothing. Other than theisaivar (which every class needs to have), there's nothing there. No superclass, no other instance variables, no methods.

What, then, happens when we try to message an instance of this empty class? I put[obj self]in the code after destroying the object and then ran it ingdb. Here's the result:

2011-05-1914:42:39.427a.out[62888:a0f]***-[Dummyself]:messagesenttodeallocatedinstance0x1001106b0ProgramreceivedsignalSIGTRAP,Trace/breakpointtrap.0x00007fff82a4d6c6in___forwarding___()(gdb)bt#0  0x00007fff82a4d6c6 in ___forwarding___ ()#1  0x00007fff82a49a68 in __forwarding_prep_0___ ()#2  0x0000000100001c49 in main (argc=1, argv=0x7fff5fbff690) at zomb.m:62

The___forwarding___stuff is the part of the runtime that takes over when the target object doesn't implement the message that was sent to it. It's called "forwarding" becauseforwarding messages to other objectsis one of its major uses.

The forwarding mechanism is throwing aSIGTRAPbecause the class doesn't implement the minimum necessary forwarding methods. What's logging "message sent to deallocated instance", though? Let's put a breakpoint onCFLogand find out:

Breakpoint2,0x00007fff82a98327inCFLog()(gdb)bt#0  0x00007fff82a98327 in CFLog ()#1  0x00007fff82a4d6c5 in ___forwarding___ ()#2  0x00007fff82a49a68 in __forwarding_prep_0___ ()#3  0x0000000100001c49 in main (argc=1, argv=0x7fff5fbff690) at zomb.m:62(gdb)contContinuing.2011-05-1915:45:03.905a.out[62938:a0f]***-[Dummyself]:messagesenttodeallocatedinstance0x1001106b0

And so we can see that the runtime forwarding mechanism itself emits this log after detecting the zombie class.

Conclusion

Zombies are a really useful tool for debugging memory problems. Under the hood, zombies work by rewriting the object'sisapointer to point to a special zombie class associated with the original. When a message is sent to an instance of the special zombie class, it gets trapped by the runtime's message forwarding system which then logs the event and crashes the app.

That wraps things up for today. Come back in two more weeks for the next one, just in time for WWDC. In the meantime, as always,keep sending me your ideas for topics.

Did you enjoy this article? I'm selling whole books full of them! Volumes II and III are now out! They're available as ePub, PDF, print, and on iBooks and Kindle.Click here for more information.

Comments:

Peter Hoseyat2011-05-20 17:08:54:

This

is why one of the most common symptoms of a memory management error is a

mystery object, like a random NSString, showing up where you expected

to see something else.

It's worth including, for the benefit of searchers, that this typically manifests as a console message along the lines of “-[NSCFString objectAtIndex:]: unrecognized selector sent to instance 0x1c2b3a40”, where the class will be just about any random class and the selector will be one appropriate for a message to the object you meant to message (the one that died and has been succeeded, in this example, by a string).

Great post; thank you for writing and publishing it.

Scott Morrisonat2011-05-20 17:40:16:

As you said, one of the problems with running with zombies enabled is that it consumes a huge amount of memory.  This means that if you have an overrelease that occurs some time into the execution of your code, enabling zombies may not help because all your system resources get eating zealously creating zombies.

If you suspect/know that the overreleased object is of a certain class, is there a way to zombie only that class.  eg  NSString -- I would know this is overreleased because  at some point in the code, code starts sending it string methods messages (but by this time it is reused as a NSData object for example)  eg [NSData doesNot respond to selector stringByAppendingString: ]

Can you overload (or swizzle if need be) the dealloc of specific classes to turn them into zombies by setting the isa yourself.

This keeps memory consumption down -- but helps you identify the object that actually was over released -- and possibly where.

Aviat2011-05-20 17:58:04:

"Can you overload (or swizzle if need be) the dealloc of specific classes to turn them into zombies by setting the isa yourself."

Yep. Zombification isn't something that only Apple can do. Chrome/Chromium keeps a running treadmill of zombies to catch errors. See the code at

http://src.chromium.org/viewvc/chrome/trunk/src/chrome/browser/ui/cocoa/objc_zombie.h?view=log

http://src.chromium.org/viewvc/chrome/trunk/src/chrome/browser/ui/cocoa/objc_zombie.mm?view=log

It's pretty flexible; it allows you to have a running treadmill, only zombify certain classes, and more.

转自: https://www.mikeash.com/pyblog/friday-qa-2011-05-20-the-inner-life-of-zombies.html

相关文章

网友评论

      本文标题:[转]The Inner Life of Zombies

      本文链接:https://www.haomeiwen.com/subject/tpqxvftx.html