busyboxをスタティックバイナリ化する


busyboxは１つのバイナリで多くの標準的なコマンドが実行できるソフトウェアであり、
組み込み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)」にチェックを入れます。

![kobito.1419065806.445939.png](https://qiita-image-store.s3.amazonaws.com/0/13343/1180e1ea-7f15-ca62-379c-d8da0022a79c.png "kobito.1419065806.445939.png")

ビルドオプションを変更したら`$ 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ほどスタティックバイナリの方が早くなっており、ちょうど１回あたり4msecの差の1000倍ぐらいになっています。

# まとめ

スタティックバイナリ化の効果は以下の通りになります。

|busybox|動的リンク|ファイルサイズ|実行に必要な環境の領域|実行時間|
|-----------------:|:--:|:--:|:--:|:--:|
| 通常              |有  |小   |大  |遅  |
|スタティックバイナリ化|無   |大   |小  |早  |

スタティックバイナリ化するかどうかはbusyboxの用途次第。
単一のバイナリのみで動くので、システムが壊れたとき非常用として持っておくもあり。

今回はスタティックバイナリ化の効果を確認するためにビルドからしましたが、
「busybox-static」というパッケージもあるのでお手軽に導入することもできます。