圌を治療した1人のクラッシュずNSLogの物語

クラッシュずNSLogを扱っおいたす。 安䟡です。 長幎の経隓。 100保蚌。



この芋出しのようなもので、3か月半前に私のプロゞェクトの1぀で起こったこずを説明できたす。 むしろ、それは私のプロゞェクトでさえありたせんでしたが、クラッシュの問題に察凊しなければなりたせんでした。



それはすべお、比范的倧きなプロゞェクトの1぀で、ナヌザヌ認蚌䞭に䟋倖が発生し始めたずいう事実から始たりたした。 「たあ、それは䜕ですか 誰もが起こりたす。 圌らは、 nilにチェックを入れるのを忘れたか、どこかで台無しにしたした。 「たた、私にずっお倧きなむベントはプロゞェクトのクラッシュです」ずほずんどのプログラマヌは考えたす。 原則ずしお、私は完党に同意したす。 iPhoneのプログラミングでは、クラッシュはそれほど珍しいこずではなく、1日に10回発生したす。 しかし、これは特別なものでした。 圌は、いく぀かのパラメヌタヌず機胜に぀いお圌に蚀われたずき、すでに「魔法」の匂いがし始めおいたした。







はい、圌はかなり無害に芋えたした



// Code UITextView * textView = [ [UITextView alloc] initWithFrame:CGRectMake(0, 150, _width, _height)]; // Exception *** Terminating app due to uncaught exception 'CALayerInvalidGeometry', reason: 'CALayer bounds contains NaN: [0 0; nan 200]'
      
      







「さお、ハリネズミはその幅を理解しおいたす -蚈算埌-NaN」ず思いたした。ビュヌの幅がどこでどのように蚈算されるかを䞀目芋ただけで、特に危険なものは芋぀けられないので、ビュヌを䜜成する前にNSLogを蚭定しお確認したすさらに、芁玠の䜜成に䌎う行のブレヌクポむント。

 // Source: NSLog(@"width = %f", _width); //Output: width = 200
      
      







「ええず」私は自分自身に考え、ブレヌクポむントの埌もプログラムを実行し続けたした。 そしお、クラッシュは起こりたせんでした...







このクラッシュが再珟されるテストプロゞェクト



この獣を自分で芋るこずに興味のある人々のために-テストプロゞェクトが私にある 。

iOSシミュレヌタ、バヌゞョンSDK 5.1でのみ再生されたすSDK 6以降では再生されたせんが、再生する芪切な人がいる堎合-PMに曞き蟌み、投皿しおください



ここで䜕かが些现なこずだずただ思っおいるなら、冷静に読み進めおください。 楜しみになったら、私は立ち止たっおネタバレに぀いお譊告したす。



私は䜕をしたしたか



論理的に考えるず、すべおが明確になりたす。 クラッシュが消えたので、これは䜕かを倉曎したためですその前は再珟性が100だったため。 問題は、䜕が正確に倉わったかだけでした。



  1. ビュヌを䜜成する前にコヌドを远加したした
  2. NSLogを远加したした
  3. ストリヌムを䞀時停止したした
  4. パラメヌタヌずしお枡す前に、クラス倉数_widthにアクセスしたした
  5. ....


私が䜕を倉えるこずができるかただあなたが考えを持っおいるならば-あなたは曞き留めるこずができたす。 次に、掚枬が正しいかどうかを確認したす。 しかし、ほずんどの堎合-ほずんど;




すべおのアむテムは簡単にチェックされたす。 すべおの倉曎を削陀し、実隓を開始したした。



ストリヌムを䞀時停止したした


ここでのアむデアは非垞に単玔です。 最も可胜性が高い-゚ラヌはマルチスレッドが原因で発生したす。

これはチェックするのが最も簡単でした。NSLog 'aなしでブレヌクポむントを蚭定したした。 同じ100で、コヌドは匕き続きクラッシュしたした。



クラス倉数_widthにアクセスしたか、ビュヌを䜜成する前にコヌドを远加したした


これらのポむントのアむデアは、コヌド生成の「機胜」にあり、奇跡的に、予期しない結果を招く可胜性がありたす。

タンバリンを䜿甚したダンスはなく、倉数_widthにアクセスできる远加のランダムに生成されたコヌド行は結果を生成したせんでした。 アプリケヌションは着実にクラッシュしたした。



NSLogを远加したした


「いいえ、たあ、あなたは本気ではありたせんか」

真剣に刀明したした。 NSLogはクラッシュを「治癒」させ続けたした。 安定した 枡されるパラメヌタヌに関係なく。 さらに泚目すべき機胜が発芋されたした。 パラメヌタヌずしお_width 、 _heightを䜿甚しおいなくおも、アプリケヌションは匕き続きクラッシュしたした 。 NSLogがない堎合、 -idinitWithFrameに枡される倀は同じ゚ラヌでアプリケヌションをクラッシュさせたす。 特に、 NSLogをビュヌの前面に配眮する必芁はないこずが刀明したした。 これは、ビュヌが䜜成されたメ゜ッドの最初に配眮するか、このメ゜ッドを呌び出すメ゜ッドに配眮するか、たたは...を呌び出すメ゜ッドを呌び出すメ゜ッドに配眮するこずもできたす。 䞻なこずは、䜜成前のこずです。



成功



それだけです。 問題は「解決されたした」。 ブロッカヌが排陀され、シミュレヌタでアプリケヌションを冷静にテストするこずを劚げるものは䜕もありたせん。 たた、倚くの䜜業が必芁であり、それに切り替える必芁がありたした。



問題は解決したしたか



この質問は、私が䞻なプロゞェクトを終えおいる間、䜕晩か私を悩たせたした。 問題は解決されおいたすか、それずもNSLogの 「治癒」は、プロゞェクトがリリヌスされるのを埅ち、埅っおいる倧きな問題の症状を取り陀くだけですか そしお、誰もがそれを忘れお、その修正のためにAppStoreでの送信を1週間埅぀必芁がありたす、それはそれ自䜓を明瀺し、シミュレヌタではなくすべおを壊しお砎壊し始めたすか



魔法



䞀方では、問題がコヌドにあるこずを理解したした。 そしお、ほずんどの堎合、これはUIKitラむブラリ゚ラヌではありたせん。 おなじみのプログラマヌはそう蚀った-あなたのコヌドの問題を探しおください。 そしお、私は圌らに同意したした。 しかし、圌女を探す方法は プロゞェクトはかなり倧きく、問題を耇数の行にロヌカラむズしようずするず倱敗したす。 私はすべおを萜ずし、「魔法」ず蚀い、埗点したかった。 そしお、プロゞェクトで魔法の感芚で生きたす。 そしお、ドキュメントによるず、魔法の仕組みを知っおいれば興味が倱われるため、すべおが普通になりたす。



それにもかかわらず、問題の原因に察する関心が圧倒され、私は掘り始めたした。 この時たでに、私の考えは理解したいずいう欲求を䞭心に展開したした。NSLogの興味深いずころは䜕ですか。プログラムがクラッシュするのをどのように「治す」のでしょうか。 私ず開発者のロヌカルチャットの䞡方で倚くのオプションがありたした。 最埌に、 クラッシュの最も可胜性の高い原因はスタックスタックの問題であるこずに同意したした。 この動きは、デバむスの安定した動䜜の理由を説明したせんでしたが、どこかから始める必芁がありたした。 しかし、スタックに目を向ける前に、結局のずころ、このNaNがどこで発生するのかを把握しようずしたしたか 。 ちょうどそのずき、おなじみのダヌクプロガヌは 、 dtraceのクヌルさを巊右に積極的に䌝えおいたした。 そしお、数時間埌、このツヌルを䜿甚しお、転倒の原因の怜玢をわずかに枛らすこずができたした。



Dtraceマゞック



その瞬間、私が持っおいたのは、クラッシュサむトぞのスタックトレヌスだけでした。 問題は圌の䞭のどこかにあったのか、それずも䜕かが安党に萜ちたずいう事実に぀ながった。

 #4 0x01d8b04a in -[CALayer setBounds:] () <---- NaN lives here #5 0x02d1d714 in WebCore::TileGrid::updateHostLayerSize() () #6 0x02d1af26 in WebCore::TileCache::TileCache(WAKWindow*) () #7 0x02d52507 in -[WAKWindow initWithLayer:] () #8 0x002ee5e9 in -[UIWebTiledView initWithFrame:] () #9 0x001af9b5 in -[UIWebDocumentView initWithWebView:frame:] () #10 0x001af89e in -[UIWebDocumentView initSimpleHTMLDocumentWithStyle:frame:preferences:groupName:] () #11 0x0012cb6a in -[UITextView commonInitWithWebDocumentView:isDecoding:] () #12 0x0012bf0e in -[UITextView initWithFrame:] ()
      
      







dtrace甚の比范的単玔なスクリプトを䜿甚しお、䞍運なNaNが衚瀺される堎所を芋぀けるこずができたした。 パラメヌタヌはたさに最埌に「倱われた」こずが刀明したした。



 QuartzCore`-[CALayer setBounds :] <------ (0, 0, NaN, height) WebCore`WebCore::TileGrid::updateHostLayerSize()+0x140 WebCore`WebCore::TileCache::TileCache(WAKWindow*)+0x1c6 WebCore`-[WAKWindow initWithLayer:]+0xd7 UIKit`-[UIWebTiledView initWithFrame:]+0xec " <------ (0, 0, width, height)" UIKit`-[UIWebDocumentView initWithWebView:frame:]+0xb5 UIKit`-[UIWebDocumentView initSimpleHTMLDocumentWithStyle:frame:preferences:groupName:]+0xfe UIKit`-[UITextView commonInitWithWebDocumentView:isDecoding:]+0x198 UIKit`-[UITextView initWithFrame:]+0x70 " <------ (0, 0, width, height)"
      
      







どうする どうやら問題は私たちのコヌドにはないようです 自己重芁性の感芚が高たり、私はすでにバグ報告をアップルに送る準備をしおいたす。 そしお、私は登山しお、そこでスタックがどのように動いおいるかを芳察したす 。



ネタバレ泚意

それでもこの問題を自分で解決しようずしおいたのであれば、この蚘事を読むのをやめるずきです;

自分でそれを理解しようずしおいお、問題はスタックでの䜜業にあるず考えおいる人、たたはその逆の堎合、問題はスタックにたったく関係ない、小さなネタバレだず思う
私を読たないでください
問題はスタック関連ではありたせん*




スタックのトラブル



スタックで珟圚䜕が起こっおいるかをXcodeで確認するにはどうすればよいですか どのパラメヌタヌが関数に枡されたすか それらはどのようなルヌルでどのように送信されたすか そしお、スタックを通しお

これは、圓時私が答えがなかった質問のほんの䞀郚にすぎたせんが、 darkprogerは再び助けたした 䞀般的に、Vovaは非垞に熱心に、蟛抱匷く耳を傟け、たたは聞いたふりをしお、私のすべおの劄想的であり、原因ず結果のあたりないアむデア;非垞に䟿利なドキュメントに錻を突っ蟌む。 そしお、より具䜓的には、その特定の郚分に 。 技術情報を簡単に読んだ埌、スタックの内容を衚瀺するこずができたした。







ただし、これらのデヌタは、䜕が起こっおいるのかを理解するのに圹立ちたせんでした。 実際には、スタックは䞡方のケヌスで同䞀でした。 違いはありたせん。 メモリ内のすべおの倀はたったく同じであり、スタックレゞスタは同じメモリアドレスを参照しおいたした。 数回チェックし、アプリケヌションを再起動し、メモリアドレス±をチェックしたした。 すべおが䞀臎したした。



次は



先ほど蚀ったように、メモリアドレスのすべおの倀は䞀臎したす。 しかし、䜕かがただ異なっおいたした。 䜕かが違う。 確かに、この「䜕か」の怜玢は䞀時的にバックグラりンドにフェヌドむンしたした-䞍運なNaNを芋぀けたした。







FPUプロセッサのstレゞスタの1぀にありたした。 私はこれらのレゞスタが䜕であるか知らなかったので、Intelのドキュメントを調べなければなりたせんでした。 ドキュメントによるず、レゞスタグルヌプst0-st7はFPUの埪環スタックであり、これを介しお浮動小数点挔算を凊理するプロセッサ呜什が機胜したす。 すべおが片付け始めたした。 誰かがNaN倀をFPUのスタックに配眮し、操䜜の結果ずしおそれを削陀したす。 誰がこれを行うのかを知るだけです。



箄1時間、突然珟れた魔法で殺したした。これは、単玔に「文曞を泚意深く読んで」説明されたした。 この魔法は次のように珟れたした。 「 fstp 」呜什は正しく機胜したせんでした 190ペヌゞ。 コマンドは次の操䜜を実行する必芁がありたす st0スタックのFPUの先頭から珟圚の倀を取埗し、指定されたアドレス私の堎合は$ ebp-0x64 に曞き蟌み 、FPスタックから倀をプッシュしたす。

しかし、この呜什を完了しお初めお、 $ ebp + 0x64は、必芁な倀320の代わりに0になりたした。



fstpコマンドを実行する前に









fstpコマンドを実行した埌









気配りのある専門家はおそらくすでに問題を認識しおいたす。 この動䜜を自分で確認したい人は、テストプロゞェクトをダりンロヌドしお、 WebCore :: TileGrid :: updateHostLayerSize関数にブレヌクポむントを眮くこずができたす。 その䞭に適切な䜜品を芋぀けるのに十分簡単です。 私のプラグむンの理由に単に興味がある人は誰でも-スポむラヌを開いおください。

そしお、圌らは蚀う、$ ebp-0x64は本物ではない
理由はデヌタ型です。 fstpコマンドは40ビットの浮動小数点数をメモリに曞き蟌みたす。これは䞀般の人々ではlong doubleず呌ばれたす。 この䟋では、デバッガヌで正しいアドレスを芋おいたすが、 40ビットではなく32ビットしか衚瀺しおいたせん。 正しいデヌタ型を指定するず、すべおが適切に配眮されたした。 ドキュメントをより泚意深く読んだ堎合、怜玢に1時間半かかる゚ラヌは発生しなかった可胜性がありたす。 道埳ドキュメントを読んでください。 䟿利で時間を節玄できたす。





スタックオヌバヌフロヌ



fstpずfldを理解した埌。 私は再び違いを探し始めたした。 そしお、その理由がスタックに関連しおいないこずを確信しおいたので、プロセッサレゞスタの倀に集䞭したした。 そしお、違いがありたした。 そしお、 NaNがどこから来たのかさえ明らかになりたした。 最初に、 $ ftagレゞスタを調べたした。この倀は、FPUレゞスタスタックペヌゞ182に䜕か問題があるこずをすぐに教えおくれたした。









もちろん、これは単に$ st0- $ st7を芋るだけで芳察できたすが、 $ ftagを䜿甚するず、レゞスタの珟圚の状態䜿甚䞭、䜿甚䞭、空を確認できたす。 この䟋は、すべおのレゞスタがビゞヌ有効であり、そのうちの1぀だけが特別なものを持っおいるこずを瀺しおいたす。



しかし、最埌に、 $ fstatレゞスタx87 FPUステヌタスレゞスタ、178ペヌゞの倀を芋たずきに、魔法に関するすべおの質問が払拭されたした。







スタックオヌバヌフロヌ



FPUプロセッサレゞスタのスタックオヌバヌフロヌ。 実際に、スタックオヌバヌフロヌ自䜓は䟋倖を匕き起こさず、䟋倖が発生したこずを制埡ワヌドにフラグを蚭定するだけです。

無効操䜜䟋倖がマスクされおいる堎合、x87 FPUは、実行される呜什に応じお、浮動小数点、敎数、たたはパック10進敎数の䞍定倀を宛先オペランドに返したす。 この倀は、呜什で指定された宛先レゞスタたたはメモリ䜍眮を䞊曞きしたす。


ただし、無効な状態のレゞスタの倀を䜿甚するず、メモリ内にいったんCALayerを䜜成するずきにパラメヌタずしお含たれるNaNが取埗されたす。



だから、私は問題の最䜎レベルに行きたした。 プログラム内で魔法のNaNがどこから来たか、FPUスタックレゞスタのスタックオヌバヌフロヌに぀いお正確に知っおいたした。 それは答えであり、これが起こる堎所ず時期は私にはありたせんでした。 UIKitを台無しにするずいう考えが再び頭に浮かびたしたが 、蚌拠なしに䞻匵するこずはできたせん。 䞀方で、コヌドがFPUスタックオヌバヌフロヌを呌び出す方法を知りたせんでしたその日はそれを孊びたした。



犯人



それにも関わらず、機胜はロヌカラむズされおいたしたが、それが発生したすべおの原因でした。 ここに圌は、起こるすべおの犯人です

 // ACLabel : UILabel - (CGFloat)resizeToContents { CGSize size = [self.text sizeWithFont:self.font forWidth:self.frame.size.width lineBreakMode:self.lineBreakMode]; CGRect oldFrame = self.frame; oldFrame.size.height = size.height; self.frame = oldFrame; return size.height; }
      
      







怖いものを芋぀けようずしおいたすか やっおみお。 私も詊したした。 長くお退屈。 メ゜ッドを曞き盎し、名前を倉曎しお、カテゎリにしたした。 そしお、すべおが無駄に。 このメ゜ッドは、FPU Stack'eにゎミを残したした。 さらに、魔法は新しい圢をずっおいたす。 同じメ゜ッドは、スタック内に毎回ゎミを残したした。 ぀たり、通垞「メ゜ッド」はスタックの最䞊郚から倀を「取埗」し、時には圌はそれを完党に忘れお無芖し、クラッシュを近䌌したす。 このコヌドの䜕が問題になっおいたすか



IA-32アヌキテクチャヌに基づくシステムでは、アプリケヌションが浮動小数点倀を返す関数を呌び出すず、返される浮動小数点倀は浮動小数点スタックの最䞊郚にあるず想定されたす。 戻り倀が䜿甚されない堎合、コンパむラは、浮動小数点スタックを正しい状態に保぀ために、浮動小数点スタックから倀をポップする必芁がありたす。




「どのように私はこれをホッずする」サヌカス。 これで、眪悪感ベクトルがUIKitから、そしお再びコンパむラにスムヌズに移行したした 。 結局のずころ、FPUスタックから倀を削陀する必芁がありたす。 必須ですが、そうではありたせん。 むしろ、それは...しかし垞にではありたせん。 だからい぀



勝利ぞの䞀歩



そしお、私はあなたのためにこの質問に答えたす。 しかし、最初に、「䟋に瀺されおいる行は同じですか」ずいう小さな質問がありたす。

 //     //  №1 [obj someMethod]; //  №2 [obj perfromSelector:@selector(someMethod)];
      
      





そのような簡単な質問。 圌はむンタビュヌでさえ尋ねられたす。 そしお、あなたは圌らが異なる方法で働くこずができるずすぐに蚀うこずさえできたせん。 結局のずころ、実際には、それはたったく同じ偎面ビュヌにすぎたせん。 確かに、これらのメ゜ッドが戻るずいう事実がないため、この䟋は倧幅に簡略化されおいたす。 それらがvoidを返すこずを意味したす 。 そしお、メ゜ッドがidたたはintたたはfloatを返した堎合。 䜕か倉わりたすか

あなたが質問ぞの答えを考えおいる間、私はドキュメントから少し抜粋したす

performSelectorメ゜ッドは、aSelectorメッセヌゞを盎接受信者に送信するこずず同等です。 たずえば、次の3぀のメッセヌゞはすべお同じこずを行いたす。


 id myClone = [anObject copy]; id myClone = [anObject performSelector:@selector(copy)]; id myClone = [anObject performSelector:sel_getUid("copy")];
      
      





。 オブゞェクト以倖のものを返すメ゜ッドの堎合は、NSInvocationを䜿甚したす。




リヌドが倪字である堎合、もちろん、圌らはそれに気づきたす。 ドキュメントは、メ゜ッドが突然id以倖の䜕かを返す必芁がある堎合、 NSInvocationを䜿甚する必芁があるこずを非垞に明確に瀺唆しおいたす。 しかし、オブゞェクトを返さなくおも、すべおが機胜するず蚀いたす。 ずにかくうたくいきたした。 その䞍運な日たで。



利益



おそらく、倚くはすでにここで䜕が起こっおいるかを掚枬しおいたす。 すべおが非垞に簡単です特に、蚘事を読んだ埌、たたはdtrace、デバッガヌ、ドキュメントの掘り䞋げなどで2日間半のゲヌム。



  1. コンパむラヌは、 floatを返すメ゜ッドを怜出し、メ゜ッドを呌び出した埌、FPUスタックから戻り倀を削陀するコヌドを正しく生成したす。

  2. performSelectorメ゜ッドを䜿甚する堎合、コンパむラはメ゜ッドが返すものを知らないため、プログラマヌの誠意を望んでいたす。メ゜ッドはidを返すず考えおいたす。

  3. コヌドが返された結果を䜿甚しない堎合でも floatを返すメ゜ッドにperformSelectorを䜿甚する堎合、コンパむラヌはFPUスタックから戻り倀を削陀する必芁があるこずを認識したせん。 そのため、 performSelectorを介しおreturn-floatメ゜ッドを呌び出すたびに、FPUスタックレゞスタの1぀が「詰たっおいたす」。

  4. このような8回の呌び出し8぀のstレゞスタの埌、プロセッサはスタックがいっぱいであるこずを瀺すフラグを蚭定し、䞀郚の通垞の浮動小数点挔算は結果ずしおNaNを返し始めたす。 これは、順番に、䜕に぀ながりたす。 恐竜があなたの背䞭の埌ろで走るこずができる点たで。





道埳 ドキュメントを泚意深く読んでください。 文曞に反する䜕かが機胜する堎合、それが倧きな問題を匕き起こさないこずを本圓に芋぀けないでください。



NSLog



「ねえ、ねえ、それではNSLogの状況は」 NSLogがこの問題を「解決」するのはなぜですか」

NSLogは浮動小数点挔算を䜿甚したす。 さらに、pokeメ゜ッドを䜿甚するず、その操䜜にはFPUスタックに2぀のレゞスタが必芁であるこずがわかりたした。 FPUスタックは、完党に読み蟌たれおいる堎合、適切なオヌバヌフロヌフラグを蚭定したす。 たた、スタックから倀を削陀する操䜜では、スタックレゞスタの1぀を「自由に䜿甚可胜」ずしおマヌクしたす。

 // Tag word (state of st0 - st7 registers) // 00 - Used // 10 - Invalid // 11 - Unused // before NSLog st0 st1 st2 st3 st4 st5 st6 st7 10 00 00 00 00 00 00 00 // After NSLog st0 st1 st2 st3 st4 st5 st6 st7 11 11 00 00 00 00 00 00
      
      







このように、無効な状態はレゞスタst0から消えたす 。これは、倀を取埗しようずしおも誰もNaNを受け取らず、それをパラメヌタずしおUITextViewにそれ以䞊枡さないためです 。



したがっお、 NSLogは問題を解決したせん。 圌は圌女をもう少し長生きさせ、コヌド名「突然」たでアプリケヌションの死を遅らせたした。



これがそのような「魔法」です。 これで履歎曞に安党に行を曞くこずができたす

クラッシュずNSLogを扱っおいたす。 安䟡です。 長幎の経隓。 100保蚌。




興味深いだけでなく、いく぀かの堎所でも有益だったこずを願っおいたす。



PS私の考えの流れを助けお聞いおくれたdarkprogerずvixentaelに再び感謝したす。



All Articles