nRF52840 DKで遊ぼう⑦~バイナリ肥大化の謎を追う~
はじめに
Zephyrのアプリケーションを作成しています。 すると、バイナリが肥大化する現象が発生したため、原因を調査しました。
結論から言うと、ライブラリを作る際、コンパイラにPIC (Position Independent Code) の無効化を指定し忘れていたことが原因でした。
発生した現象
nRF52840をターゲットにZephyrをビルドする場合、最終的な出力は、Intel HEXフォーマットになります。 そのため、Zephyrのビルドプロセスでは、elfファイルを作成した後に、objcopyで生バイナリにした後、hexファイルを作成します。
正常な場合、elf、バイナリ、hexは、それぞれ次の通りのサイズになります。
-rwxrwxr-x 1 tomoyuki tomoyuki 89K 3月 20 12:57 zephyr.bin -rwxrwxr-x 1 tomoyuki tomoyuki 1.6M 3月 20 12:57 zephyr.elf -rw-rw-r-- 1 tomoyuki tomoyuki 251K 3月 20 12:57 zephyr.hex
一方、問題発生時は次の通り、バイナリとhexが異常なサイズになっていました。
-rwxrwxr-x 1 tomoyuki tomoyuki 513M 3月 19 08:59 zephyr.bin -rwxrwxr-x 1 tomoyuki tomoyuki 1.6M 3月 19 08:59 zephyr.elf -rw-rw-r-- 1 tomoyuki tomoyuki 1.5G 3月 19 08:59 zephyr.hex
原因を突き止める
バイナリをいじくりまわしたところ、.got
セクションが悪さをしていることが分かりました。
Disassembly of section .got: 20000000 <_GLOBAL_OFFSET_TABLE_>: ... 2000000c: 000146a0 andeq r4, r1, r0, lsr #13
.gotセクションが、0x2000_0000番地に、16バイトのデータを配置するため、生バイナリに変換すると、512MBのファイルになります。 最初の数十KBの位置に正常なデータが置かれた後、0x2000_0000番地までは、パディングのデータが入っていました。
.gotセクションが本当に問題かどうか、確認してみます。試しに、elfファイルから.got
セクションを取り除いて、生バイナリに変換してみると、意図したサイズのバイナリが出力されました。
arm-none-eabi-objcopy -O binary zephyr.elf self.bin -R .got 89K 3月 19 10:02 self.bin
原因は、この.gotセクションがelfに入ってくるせいです。
では、この.gotセクションが、なぜelfに入り込んでくるのでしょうか? twitterで困っていると、@NerryN3 から、ダイナミックリンクする場合やPIEを行う場合に入るセクションであることを教えて頂きました。
バイナリは全てスタティックリンクしているはずなのですが…。
$ file zephyr.elf zephyr.elf: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, not stripped
elfもstatically linked
となっています。
怪しいのは、自分が作ったライブラリ部分です。 そこで、コンパイラオプションを調べたところ、PICが無効化されていませんでした。
PICを無効化するオプションを付けて、ソースコードをビルドしたところ、無事.got
セクションが消えて、小さなバイナリが手に入りました。