[转]The Inner Life of Zombies

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,128评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,316评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,737评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,283评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,384评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,458评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,467评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,251评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,688评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,980评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,155评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,818评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,492评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,142评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,382评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,020评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,044评论 2 352

推荐阅读更多精彩内容

  • rljs by sennchi Timeline of History Part One The Cognitiv...
    sennchi阅读 7,319评论 0 10
  • The Inner Game of Tennis W Timothy Gallwey Jonathan Cape ...
    网事_79a3阅读 11,990评论 3 20
  • 万事开头难,尝试过后,方知个中滋味,也会是今后的茶余饭后的谈资。 演讲对于我来说,头一次尝试的,让我很是愁绪。虽然...
    沐府墓主阅读 336评论 0 0
  • 文 / 戒闲 (12)做局 高木最近觉得阿欣很奇怪,妆扮精致的让他有些陌生,他也试图跟阿欣坐下来谈谈,但被阿欣敷衍...
    花一尧阅读 194评论 0 0
  • 今年很多地方,学校换了部编版新教材,拼音学习往后移,适当降低难度,强调学习在生活中的运用。很多老师,家长,孩子闻之...
    卉山居阅读 519评论 0 2