busyboxは1つのバイナリで多くの標準的なコマンドが実行できるソフトウェアであり、
組み込みLinuxなどのように領域に制限のある環境などで使用されることが多い。
また、busyboxは多くの一般的なプログラム同様にlibcなどの汎用的な機能を使用する動的ライブラリにリンクしています。
今回はbusyboxのビルドオプションを変更し、動的リンクして使用していたコードをバイナリに組み込む、スタティックバイナリ化します。
スタティックバイナリ化の目的は後ほど追って説明します。
今回は、busybox 1.22.1をビルドして使用します。
動作環境はRaspberry Pi(Model B)です。
busyboxを普通にビルドする
busyboxのソースコードをダウンロードしてビルドします。
$ wget http://busybox.net/downloads/busybox-1.22.1.tar.bz2
$ tar xvf busybox-1.22.1.tar.bz2
$ cd busybox-1.22.1
$ make
カレントディレクリにbusyboxのバイナリが出来上がったので、動的ライブラリにリンクしているかldd
コマンドで調べてみます。
$ ldd busybox
/usr/lib/arm-linux-gnueabihf/libcofi_rpi.so (0xb6f4a000)
libm.so.6 => /lib/arm-linux-gnueabihf/libm.so.6 (0xb6ecd000)
libc.so.6 => /lib/arm-linux-gnueabihf/libc.so.6 (0xb6d9e000)
/lib/ld-linux-armhf.so.3 (0xb6f58000)
libcやlibmに動的ライブラリにリンクしていることが確認できます。
次にstrace
を使ってbusyboxが動的ライブラリを呼び出していることを確認します。
$ strace -e trace=open ./busybox > /dev/null
open("/etc/ld.so.preload", O_RDONLY) = 5
open("/usr/lib/arm-linux-gnueabihf/libcofi_rpi.so", O_RDONLY) = 5
open("/etc/ld.so.cache", O_RDONLY) = 5
open("/lib/arm-linux-gnueabihf/libm.so.6", O_RDONLY) = 5
open("/lib/arm-linux-gnueabihf/libc.so.6", O_RDONLY) = 5
openシステムコールが動的ライブラリを呼び出していることが確認できました。
busyboxスタティックバイナリでビルドする
$ make menuconfig
を実行してbusyboxのビルドオプションを変更します。
「Busybox Settings ---> Build Options --->Build BusyBox as a static binary (no shared libs)」にチェックを入れます。
ビルドオプションを変更したら$ make
で再度ビルドします。
出来上がったバイナリが変更通り、動的ライブラリにリンクしていないことを同様にldd
とstrace
を使って確認します。
$ ldd busybox
not a dynamic executable
$ strace -e trace=open ./busybox > /dev/null
変更前と違い動的ライブラリを呼び出していないことが確認できました。
なぜスタティックバイナリ化するのか?
使用容量の節約
通常のバイナリとスタティックバイナリでのファイルサイズを確認すると、
$ du -k busybox
920 busybox```
```スタティックバイナリ
$ du -k busybox
1840 busybox
スタティックバイナリの方が900K弱ほどがファイルサイズが大きくなっています。
これは本来動的リンクして呼び出していたコードをbusybox自身に組み込んだ(スタティック化)したためです。
しかしながら、組み込み機器やinitrdやinitramfsのようにカーネルイメージにrootfsを組み込む場合のように使用容量に厳しい制限がありbusyboxのみで済ませようとする環境では、libcすら不要なことがあります。
このような環境でスタティックバイナリ化したbusyboxを使用することで、libcを削除することで結果として使用容量を削減することができます。
busyboxにリンクしている動的ライブラリのサイズを確認してみます。
$ du -k /usr/lib/arm-linux-gnueabihf/libcofi_rpi.so
12 /usr/lib/arm-linux-gnueabihf/libcofi_rpi.so
$ du -k /lib/arm-linux-gnueabihf/libm-2.13.so
420 /lib/arm-linux-gnueabihf/libm-2.13.so
$ du -k /lib/arm-linux-gnueabihf/libc-2.13.so
1172 /lib/arm-linux-gnueabihf/libc-2.13.s
$ du -k /lib/arm-linux-gnueabihf/ld-2.13.so
124 /lib/arm-linux-gnueabihf/ld-2.13.so
もちろん、今の環境で上記ファイルを削除すると他のプログラムが動作なくなり大変なことになりますが、
これらの動的ライブラリが不要だとした時の削減量は、busyboxのバイナリサイズの増加量よりも大きくなります。
プログラム実行時間
これまで確認してきた通り、スタティックバイナリ化する前の通常のbusyboxはプログラム実行時に動的ライブラリを呼び出しており、
この呼び出し処理の有無によって非常に些細ですがプログラム実行時間に差がでます。
$ time ./busybox > /dev/null
real 0m0.013s
user 0m0.000s
sys 0m0.010s
$ time ./busybox > /dev/null
real 0m0.009s
user 0m0.000s
sys 0m0.000s
4msecほどスタティックバイナリの方が早くなっています。
些細な差ですが、busyboxはlsやechoからシェル(sh)まで幅広く使用されるので、
busyboxをメインに使用しているシステムでは何千回以上も呼び出されます。
試しに1000回ほど動かしてみると
$ time for n in `seq 1000`;do ./busybox > /dev/null ;done
real 0m12.600s
user 0m2.210s
sys 0m7.540s
$ time for n in `seq 1000`;do ./busybox > /dev/null ;done
real 0m8.355s
user 0m0.600s
sys 0m1.570s
約4.3sほどスタティックバイナリの方が早くなっており、ちょうど1回あたり4msecの差の1000倍ぐらいになっています。
まとめ
スタティックバイナリ化の効果は以下の通りになります。
busybox | 動的リンク | ファイルサイズ | 実行に必要な環境の領域 | 実行時間 |
---|---|---|---|---|
通常 | 有 | 小 | 大 | 遅 |
スタティックバイナリ化 | 無 | 大 | 小 | 早 |
スタティックバイナリ化するかどうかはbusyboxの用途次第。
単一のバイナリのみで動くので、システムが壊れたとき非常用として持っておくもあり。
今回はスタティックバイナリ化の効果を確認するためにビルドからしましたが、
「busybox-static」というパッケージもあるのでお手軽に導入することもできます。