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セクションが消えて、小さなバイナリが手に入りました。