static変数に対応するアドレスを探す(ELF format編)

共有ライブラリ上に存在する、あるstatic変数aのプロセス上でのアドレスを知りたいときがあります。apt-getでインストールした共有ライブラリをデバッグしたい時なんかがそうです。
最近、こういったアドレスを探すことが多いので、忘れないようにメモっておきます。


以下の話は、共有ライブラリが位置独立コード(PIC)としてコンパイルされていることを前提にしています。
あと、私はgccx86系cpuを使って確認しているので、他の環境だと違う場合があります。

基本

共有ライブラリを使っているプロセスのメモリ利用状況をpmapコマンドやprocファイルシステムを使って確認することで、共有ライブラリのロード先がわかります。
共有ライブラリ名がhoge.soだとすると、

 $ pmap 10000
 08048000    4K r-x-- a.out
 ...<>...
 b7f8e000    8K rwx-- foo.so
 b7f94000    4K r-x-- hoge.so
 b7f95000    4K rwx-- hoge.so
 ...<>...

上のpmapの結果から
hoge.soのロード先先頭アドレスは、b7f94000だとわかります。また、シンボルテーブルにstatic変数aの情報があるなら、簡単にaの共有ライブラリ内での相対アドレスがわかります。

あとは、以下の計算式、
(プロセス上での変数aのアドレス) = (共有ライブラリのロード先頭アドレス) + (変数aの相対アドレス)
で変数aのアドレスがわかります。

まあ、こんなのいちいち計算しなくても、gdbがやってくれます。gdbでプロセスにアタッチして、

 p &a

とかすれば、変数aのアドレスがわかります。

シンボルテーブルがない場合

apt-getでインストールした共有ライブラリの場合、ファイルサイズを減らすために、シンボルテーブルがstripコマンドで削除されていることがあります。

この場合、簡単には変数aの相対アドレスを知ることができません。プログラムを実行させずに相対アドレスを調べるには、アセンブリコードを読んで相対アドレスを探すしかないです*1(たぶん)。

アセンブリコードだけでもよくわからないので、コンパイル元のソースコードも使います。
まず、ソースコードの中で変数aを操作する処理が入っているグローバル関数をまず探します。グローバル関数の中で変数aを操作していないのなら、グローバル関数からたどりやすい関数の内、変数aを操作する関数を探します。グローバル関数の相対アドレスは、ダイナミックシンボルテーブルに存在するので、objdumpで逆アセンブルした時、objdumpが適切にグローバル関数の先頭を表示してくれます。

最適化が影響しないかぎり、グローバル関数を基点として、ソースコードアセンブリコードを比較していけば、変数aのGOTからの相対アドレスを判別できるアセンブリ命令が見つかると思います。
例えば、以下のソースコード

 a = 0;

に対応するアセンブリコード

 movl $0x0 0x20(%ebx)

を見つけたとします。
0x20が、変数aのGOTからの相対アドレスです。

このとき、%ebxは、
%ebx = (ファイル内のGOT相対アドレス) + (共有ライブラリロード先頭アドレス)
です。

ファイル内のGOT相対アドレスは、objdump -Tコマンドで表示されるダイナミックシンボルテーブルのうち、
_GLOBAL_OFFSET_TABLE_シンボルのアドレスに対応します(gccの場合)
また、(_GLOBAL_OFFSET_TABLE_のアドレス) == (.got.pltセクションのアドレス)のようです。

共有ライブラリロード先頭アドレスは、最初の方法と同様にpmapコマンドを使って見つけられます。

以上の情報を使って、以下の計算式、
 (プロセス上での変数aのアドレス) = (変数aのGOTからの相対アドレス) + (_GLOBAL_OFFSET_TABLE_のアドレス) + (共有ライブラリロード先頭アドレス)
で変数aのアドレスがわかります。

うーん、何か説明がわかりにくいなあ。実際にアドレスを探している動画とか作ったほうがわかりやすいかも。

参考文献

GOTとかPICとかについては、以下の書籍が参考になります。

*1:ちなみに、staticではない大域変数なら、objdump -Tとか、nm -D とかして、ダイナミックシンボルテーブルから、相対アドレスを調べれば十分です。