ペンギン村 Tech Blog

技術をこよなく愛するエンジニア集団が在住するペンギン村から、世界へ役立つ(かもしれない)技術情報を発信する技術系ブログです。某アラレちゃんが済む村とは一切関係ありません。んちゃ!

メモリ上に保持されているSwiftクロージャーがどこに定義されたものかを知る方法

自己紹介

ペンギン村の通行人 、po_miyasakaです。

概要

できること

任意のタイミングでメモリ上に保持されているクロージャが定義されている場所を表示する。おそらく本邦初公開😎

使い所

手順がちょっと大変なので、どうしてもリークを確かめたい時とかに使えるかも。

方法

https://miyasakakazutoshi.github.io/closure_detect.gif

手順
  1. Debug Memory Graphを開く
  2. closureでコンポーネントを検索すると swift closure contextというクラスのインスタンスのアドレスが複数引っかかる
    • このインスタンスがクロージャにキャプチャされたデータなどのクロージャの情報のようである。
    • このデータの中を探してもクロージャの場所はわからない。
  3. 任意のアドレスを左のバーから選ぶ。
  4. 末端の swift closure context指している矢印をクリックして右のサイドバーのMemory Inspectorを表示
  5. Sourceの項目には選択した swift closure contextの格納されているアドレスが表示されている。
  6. 以下の画像の場合は0x600003554100 + 24swift closure contextのポインター0x6000035540e0が格納されていることを示している。
    • ポインターの指している中身を知る方法は後述する。 f:id:po_miyasaka:20190204073820p:plain
  7. 実は swift closure contextを指しているポインター0x600003554100 + 248byte前にクロージャ本体が格納されているのだ!
    • すなわち 0x600003554100 + 16のポインターの中身を見ることで、クロージャ本体の場所がわかる。
  8. 0x600003554100 + 16の中身をみると 0x0000000101e86e20というアドレスが取れた。
  9. このアドレスを使って image lookup -a 0x0000000101e86e20というコマンドを叩く。すると以下のように出力される。 f:id:po_miyasaka:20190204074629p:plain
  10. この出力のSummaryを見ると MainViewController.viewDidLoadに定義された一番目*1のクロージャで有ることがわかる。
  11. つまり以下のクロージャであることがわかる。 f:id:po_miyasaka:20190204075317p:plain

リファレンスからデリファレンスする方法

  • リファレンスはポインターのことで、デリファレンスはポインターが指しているものを取得することです。
    • 上記の8番における0x600003554100 + 16がリファレンスで、これをデリファレンスすると0x0000000101e86e20が取得できるということです。
x/gx コマンドを使う。
  • 純粋にアドレスを取得することができる。
    f:id:po_miyasaka:20190204221827p:plain
expression -l objc -o -- *(id**)を使う
  • 長いのでエイリアスを作ったほうがいい*2
  • この方法でいろんなポインタを見てみるとクラス名が表示されたり、idcharに変えると文字列が取得できたりするので面白い。

f:id:po_miyasaka:20190204221811p:plain
f:id:po_miyasaka:20190204221751p:plain

キャプチャされたデータが知りたい場合

上述の expression -l objc -o -- *(id**)がクラス名を表示してくれる特性を利用して、 以下の画像のようにswift closure context自体のメモリ領域を8バイトずつシラミ潰しにチェックしていけばキャプチャされているクラスが見つかるはず。*3

f:id:po_miyasaka:20190205234511p:plain
キャプチャされたデータを見つける
ここで見つかったクラスをコンソールで変数化してアクセスするのにはvinfoが便利

動作環境

備考

  • 動作確認したサンプルが少ないので、例外などを見つけ次第追記する。

脚注

*1:#1や#2のような数字は定義されたクロージャに順番に振られる番号

*2:エイリアスの作成については別途まとめる。

*3:Structの場合は型名は表示されない。