ldd(1) の動的な側面が悪い方向に際立つ例

思えば1年半も前のことだったが... 分かりやすい例に仕立ててみよう.以下の内容のCソース hugebss.c を作る:

char arr[sizeGb*1024ULL*1024ULL*1024ULL];
int main() { }

長い配列 arr のサイズを変えて2通りにコンパイルし, ldd(1) してみる:

$ gcc -DsizeGb=1ULL hugebss.c -o 1gb.exe
$ ldd 1gb.exe
        linux-vdso.so.1 =>  (0x00007fffa1799000)
        libc.so.6 => /lib/libc.so.6 (0x00007f338e5f6000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f338e96a000)
$ gcc -DsizeGb=64ULL hugebss.c -o 64gb.exe
$ ldd 64gb.exe
        not a dynamic executable

初めて見ると「え? あれ?」みたいな(笑
"not a dynamic executable" とか適当なことをほざく ldd(1) というシェルスクリプトが,中で何をやってるのか見ればすぐ分かるんだけどね.実際の仕事はOSコアの一部の動的ローダ ld-linux(8) (/lib/ld-linux.so) が実際にロードすることで行っており, ldd(1) は引数の処理とかエラー処理とかのラッピング担当なんだが,その際に逆に ld-linux(8) がエラー時にきちんと出している診断を握りつぶしておかしなメッセージを出しているのだった.このいい加減さがLinuxらしいと言うのかな.中を追うには,まず cp `which ldd` ldd; vim ldd して,2行目に set -x して実行すると((私の環境の dash と bash では /bin/sh -x が使えるけど,これってどのくらい可搬性あるんだろうか... bash で使えるならubiquitousと言ってよいか.)),各行の実行がトレースされるので分かりやすい.
(追記: 新しくファイル作らなくても bash -x `which ldd` your-command でいける.)
肝である動的ローダの実行だけ取り出して自分の手でやってみよう.bssセクションで 64 GB もメモリを消費する 64gb.exe のロードに失敗し*1,以下のように適切なエラーを表示するのが分かる:

$ /lib/ld-linux-x86-64.so.2 --list ./1gb.exe
        linux-vdso.so.1 =>  (0x00007fff545bf000)
        libc.so.6 => /lib/libc.so.6 (0x00007fefa4db0000)
        /lib64/ld-linux-x86-64.so.2 => /lib/ld-linux-x86-64.so.2 (0x00007fefa5124000)
$ /lib/ld-linux-x86-64.so.2 --list ./64gb.exe
./64gb.exe: error while loading shared libraries: ./64gb.exe: cannot map zero-fill pages: Cannot allocate memory

これが "not a dynamic executable" に化けるんだからひどい話だ.Windows用ツール dependencyWalker は同じ役割だがPE (Portable Executable) ヘッダを静的に解析してる気がする.だから上記のような問題は起きない.動的に解析しない方がよいと思うんだが... まぁパスの解決の問題がややこしいし,上記のような妙な場合以外は「有り物」を使って楽に済ませるのがよいということか.
Linuxにだって実行形式ファイルを静的に解析して属性を表示するツールは readelf(1), objdump(1) など何種類もあるが,一番単純な binutilssize(1) の動作を見てみよう:

$ size 1gb.exe
   text    data     bss     dec     hex filename
   1052     512 1073741856      1073743420      4000063c        1gb.exe
$ size 64gb.exe
   text    data     bss     dec     hex filename
   1052     512 68719476768     68719478332     100000063c      64gb.exe

これこそが静的なツールの動作で,いくらbssセクションがでかくたって数字を読み上げるだけのかんたんなお仕事だ.
あーもちろん,ldd(1) は動的リンクされる .so を探すだけじゃなくて,マップされる仮想アドレスの情報という貴重なものも出してくれるんだけど... 世の中の 99.99 % の人は => より右を読んでないと思うんだよな.もし読むなら以下と合わせてどうぞ:

*1:いや... あなたの実行環境によっては失敗しないかもしれないが... どこの研究機関にお勤めですか? それともデータセンター? ヘッジファンド