ダンプ解析
シンボルファイルとソースファイルがある場合
Visual Studioやwindbgでシンボルファイルを指定することで、デバッグ感覚でスタックや変数の状態を確認できます。
シンボルファイルのみの場合
ソースファイルとの紐づけがしにくいですが、スタックも変数の状態も確認できます。
シンボルもない場合
アセンブリを読めるひとならまあ何とかなる。
しかし、大きい規模のものになると、一筋縄では行きません。
最適化が有効だと、元のソースコードとアセンブラの結びつけが直観的にできなくなるからです。
最適化
最適化を行うと、その名の通りプログラムが効率よく動作するようにコンパイルを行ってくれます。しかし、生成されたアセンブリは元のコードと配置が異なります。
例えば、以下
- 関数として定義したのにインライン関数扱いになり、呼び出し元に組み込まれるようになる
- 変数を複数宣言して使用したが、1つのレジスタにまとめられて扱かわれ、ある時点で参照すると一部変数の状態が確認できなくなる
mapとcod
本題です。
mapとcodを作成することで、シンボルがないダンプファイルを解析して行きます。
先にそれぞれのファイル内容を紹介します
-
codファイル
ソースファイルとアセンブリの紐づけができます。
これを見ることで、どのアドレス、どのレジスタがどの変数に割り当たっているか、またどこが実行されているかを確認する事ができます。
codファイルはobjファイル単位で作成されます。
シンボルファイルがない場合、コンパイルを行ったバージョンのソースコード及び、同じビルド環境を用意します。
ビルド環境については多少のブレが許されますが、できるだけ同じ環境が良いです。具体的にはコンパイラ、リンカのバージョンや最適化オプションの有無です。
Visual Stutioでは以下から設定できます。
出力先フォルダはデフォルトで中間ファイルフォルダと、成果物の出力先フォルダで分かれているのでお気を付けください。
実際に使ってみる。
-
実行中関数の特定
実行アドレスは、実行関数から+ X Byteの箇所を指しているため、mapのRvaを見て、実行中の関数を探します。
Visual Studioの表示ではアドレス表示が絶対値となっていたため、相対値を計算します。
0x7ff76c9519ec - 0x7ff76c95000 = 0x19ec
0000000140000000はベースアドレスのため、これはあらかじめ引いて計算します。
すると以下の関数内を実行中という事が分かります。
つまり、実行されている箇所はmain+0x3ec
という事が分かります。 -
関数内のどの処理を実行中か特定
今度はcodファイルから実行箇所を見てみましょう。まずmain関数を探します。
次に、+0x3ec進んだ箇所のアセンブリを見てみます。
上図では、ポインターに代入を行っている処理が確認できます。
アセンブリから察するにptr変数はrsiのレジスタが割り当てられており、esiは0が格納されているのでしょう。 -
答え合わせ
実際の実行アセンブリと、レジスターを見てみます。
同じアセンブリコードが確認できました。
変数の状態や実行箇所が確定できましたね。
考察
元のソースは以下でmainとは別の関数内でクラッシュを発生させるようにしていました。
最適化によりmain関数にインライン化されたためこのようなmapとcodとなっていました。
まとめ
exe、dll、sysいずれにも適用可能です。シンボルファイルなくしちゃった~という方はお試しください。
また遊びで眺めるのも、アセンブリの勉強っぽくて面白いですよ。
エラーをわかりやすく出してくれるプログラムはChatGPTさん提供。
もうちょっと補足を入れた方がわかりよかったと反省。
以上です。