組込みエンジニアのためのLinux入門 ダイナミックリンク編(3) 2007.8.31 株式会社アプリックス 小林哲之
このスライドの対象とする方 今までずっと組込み機器のプロジェクトに携わってきて最近はOSにLinuxを使っている方々
このスライドの目的 Linuxで使用されているダイナミックリンクの仕組みを理解し現在のプロジェクトに役立てる。 仕組みを知らなくてもプログラムは動くが、トラブルに対処したり性能を引き出すためには仕組みの理解が重要。
今日のお題 共有ライブラリの簡単な歴史 Prelinkとは Prelinkのtips
共有ライブラリの簡単な歴史 a.out時代 ELF時代 ELF + prelink
a.out 時代 a.outはLinuxの最初のオブジェクトフォーマット。 共有ライブラリのリンクは静的に行う。 ライブラリの配置アドレスは人手によって管理されていた。 ライブラリの更新でサイズが変わると大変。 世の中の全てのライブラリを重ならないように配置しようとするとアドレス空間が足りなくなる! ライブラリの作成も大変。 起動時の処理はシンプルなので速い。
ELF時代 ELFはダイナミックリンクが簡単にできるように考慮されたオブジェクトフォーマット ダイナミックリンク ライブラリの配置アドレスは起動時に決定 ライブラリのアドレスの集中管理が不要になった ライブラリの作成も簡単になった 起動時にリンクを行う 起動時のオーバーヘッドが大きくなった
ELF + prelink a.out ELF ELF + prelink 大変 ラク 小 大 ライブラリの作成、管理 大変 ラク 起動時のオーバーヘッド 小 大 PrelinkはELFのライブラリの作成、管理の容易さを保ったまま 起動時のオーバーヘッド削減を目指す
Prelinkとは 通常は起動時に行われるリンクの処理を、 あらかじめ済ませておくことにより起動時間の短縮をねらう。 CELFテクニカルジャンボリーでもたびたび紹介され、起動時間短縮の効果が実証されている。
CELFでのprelinkの紹介 テクニカルジャンボリー #3 (2005.7) テクニカルジャンボリー#9 (2006.7) “携帯電話にLinuxを実装する!” シンボルの解決にかかる時間が2479msec →125msec テクニカルジャンボリー#9 (2006.7) “NECによる携帯電話向けリナックスの改良” テクニカルジャンボリー #13 (2007.2) “Evaluation of MIPS pre-link” Prelinkのまとめページ http://tree.celinuxforum.org/CelfPubWiki/PreLinking
Prelinkは実際に何をやってくれるのか ダイナミックリンクライブラリは起動時に配置アドレスが決まるので、その後のシンボルの解決等のローダの処理の負荷が大きかった。 Prelinkでは 全てのダイナミックリンクライブラリを走査して配置するアドレスを決定する。 それぞれのダイナミックリンクライブラリのロードアドレスを決定したアドレスにセットする. 依存関係のある実行ファイル、ダイナミックリンクライブラリのアドレス参照を解決する。 これによりローダの起動時の処理が大幅に軽減された。
prelinkの使用法 $ prelink -avR -a /etc/prelink.conf に指定してあるディレクトリのダイナミックリンクライブラリと 実行ファイルのprelinkを行う -v vorbose -R アドレスをランダムに決める (ウィルス耐性が高まる) $ prelink <filename> 追加で指定されたファイルのprelinkを行う
/etc/prelink.cache 各ライブラリのアドレスと依存関係がバイナリフォーマットで格納される。 prelink –p で確認することができる。 # prelink -p 152 objects found in prelink cache `/etc/prelink.cache' /usr/lib/libwwwfile.so.0.1.0 [0x41eeda3e] 0x4a588000-0x4a597dec: /lib/tls/libdl-2.3.3.so [0x536dae10] /lib/tls/libc-2.3.3.so [0xedb6e392] /lib/ld-2.3.3.so [0x040c82cc] /usr/lib/libsctp.so.1.0.2 [0x26ac5ae2] 0x4adc8000-0x4add12ac: /usr/lib/libreadline.so.4.3.old [0xf90fc4bc] 0x41418000-0x4144b770: /usr/lib/libwwwnews.so.0.1.0 [0xdc63747e] 0x4ac78000-0x4ac86310: ....
通常のライブラリ ロードアドレスが0 $ readelf -l libc-2.3.3.so Elf file type is DYN (Shared object file) Entry point 0x13708 There are 11 program headers, starting at offset 52 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align EXIDX 0x0fbc3c 0x000fbc3c 0x000fbc3c 0x01310 0x01310 R 0x4 PHDR 0x000034 0x00000034 0x00000034 0x00160 0x00160 R E 0x4 INTERP 0x0fb410 0x000fb410 0x000fb410 0x00014 0x00014 R 0x4 [Requesting program interpreter: /lib/ld-linux.so.3] LOAD 0x000000 0x00000000 0x00000000 0xfcf78 0xfcf78 R E 0x8000 LOAD 0x0fda64 0x00105a64 0x00105a64 0x02628 0x04da8 RW 0x8000 DYNAMIC 0x0fef1c 0x00106f1c 0x00106f1c 0x000e0 0x000e0 RW 0x4 NOTE 0x000194 0x00000194 0x00000194 0x00020 0x00020 R 0x4 NOTE 0x0fdafc 0x00105afc 0x00105afc 0x00074 0x00074 R 0x4 TLS 0x0fdae0 0x00105ae0 0x00105ae0 0x00008 0x00028 R 0x4 GNU_EH_FRAME 0x0fcf4c 0x000fcf4c 0x000fcf4c 0x0002c 0x0002c R 0x4 GNU_RELRO 0x0fda64 0x00105a64 0x00105a64 0x0159c 0x0159c R 0x1 ロードアドレスが0
prelinkしたライブラリ ロードアドレスが 割り当てられている $ readelf -l libc-2.3.3.so Elf file type is DYN (Shared object file) Entry point 0x491c3708 There are 11 program headers, starting at offset 52 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align EXIDX 0x0fbc3c 0x492abc3c 0x492abc3c 0x01310 0x01310 R 0x4 PHDR 0x000034 0x491b0034 0x491b0034 0x00160 0x00160 R E 0x4 INTERP 0x0fb410 0x492ab410 0x492ab410 0x00014 0x00014 R 0x4 [Requesting program interpreter: /lib/ld-linux.so.3] LOAD 0x000000 0x491b0000 0x491b0000 0xfcf78 0xfcf78 R E 0x8000 LOAD 0x0fda64 0x492b5a64 0x492b5a64 0x02628 0x04da8 RW 0x8000 DYNAMIC 0x0fef1c 0x492b6f1c 0x492b6f1c 0x000e0 0x000e0 RW 0x4 NOTE 0x000194 0x491b0194 0x491b0194 0x00020 0x00020 R 0x4 NOTE 0x0fdafc 0x492b5afc 0x492b5afc 0x00074 0x00074 R 0x4 TLS 0x0fdae0 0x492b5ae0 0x492b5ae0 0x00008 0x00028 R 0x4 GNU_EH_FRAME 0x0fcf4c 0x492acf4c 0x492acf4c 0x0002c 0x0002c R 0x4 GNU_RELRO 0x0fda64 0x492b5a64 0x492b5a64 0x0159c 0x0159c R 0x1 ロードアドレスが 割り当てられている
prelinkしたライブラリ この3つのセクションが追加された。 その分だけファイルサイズ増加。 # objdump -h libc-2.3.3.so libc-2.3.3.so: file format elf32-littlearm Sections: Idx Name Size VMA LMA File off Algn 0 .note.ABI-tag 00000020 491b0194 491b0194 00000194 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA, LINK_ONCE_SAME_CONTENTS 1 .hash 00002e74 491b01b4 491b01b4 000001b4 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA ... 69 .gnu_debuglink 00000018 00000000 00000000 001008f4 2**2 CONTENTS, READONLY 70 .gnu.liblist 00000014 00000000 00000000 0010090c 2**2 71 .gnu.libstr 00000014 00000000 00000000 00100920 2**0 72 .gnu.prelink_undo 00000cac 00000000 00000000 00100934 2**2 # この3つのセクションが追加された。 その分だけファイルサイズ増加。
Prelinkによるファイルサイズの増加 size(bytes) prelinked size(bytes) delta delta% libc.so 1,054,936 1,058,384 3,448 0.326844 libm.so 432,088 433,792 1,704 0.394364 libcrypt.so 27,556 29,429 1,873 6.797068 libdl.so 15,036 16,900 1,864 12.39691 libX11.so 769,468 770,988 1,520 0.197539 libglib-2.0.so 557,908 559,508 1,600 0.286786 libgtk-x11-2.0.so 2,846,996 2,849,320 2,324 0.08163 bash 615,940 645,220 29,280 4.75371 busybox 622,640 627,596 4,956 0.795966 init 28,356 31,356 3,000 10.57977 数KBの増加。
実行時のローダの動きを比較してみる 環境変数 LD_DEBUG=all で実行すると ローダの実行ログが表示される。 それをファイルに保存するためには LD_DEBUG_OUTPUT=<file name> 統計情報のみ欲しい場合は LD_DEBUG=statistics
LD_DEBUG=allの例 この例では全部で38000行 # LD_DEBUG=all ./gtk_hello ....... 559: symbol=malloc; lookup in file=./gtk_hello 559: symbol=malloc; lookup in file=/usr/lib/libgtk-x11-2.0.so.0 559: symbol=malloc; lookup in file=/usr/lib/libgdk-x11-2.0.so.0 559: symbol=malloc; lookup in file=/usr/lib/libXrandr.so.2 559: symbol=malloc; lookup in file=/usr/lib/libXfixes.so.3 559: symbol=malloc; lookup in file=/usr/lib/libXcursor.so.1 559: symbol=malloc; lookup in file=/usr/lib/libatk-1.0.so.0 559: symbol=malloc; lookup in file=/usr/lib/libgdk_pixbuf-2.0.so.0 559: symbol=malloc; lookup in file=/usr/lib/libpangoxft-1.0.so.0 559: symbol=malloc; lookup in file=/usr/lib/libXft.so.2 559: symbol=malloc; lookup in file=/usr/lib/libXrender.so.1 559: symbol=malloc; lookup in file=/usr/lib/libXext.so.6 559: symbol=malloc; lookup in file=/usr/lib/libfontconfig.so.1 559: symbol=malloc; lookup in file=/usr/lib/libfreetype.so.6 559: symbol=malloc; lookup in file=/usr/lib/libz.so.1 559: symbol=malloc; lookup in file=/usr/lib/libpangox-1.0.so.0 559: symbol=malloc; lookup in file=/usr/lib/libX11.so.6 559: symbol=malloc; lookup in file=/usr/lib/libpango-1.0.so.0 559: symbol=malloc; lookup in file=/lib/tls/libm.so.6 559: symbol=malloc; lookup in file=/usr/lib/libgobject-2.0.so.0 559: symbol=malloc; lookup in file=/usr/lib/libgmodule-2.0.so.0 559: symbol=malloc; lookup in file=/lib/tls/libdl.so.2 559: symbol=malloc; lookup in file=/usr/lib/libglib-2.0.so.0 559: symbol=malloc; lookup in file=/lib/tls/libc.so.6 559: binding file /usr/lib/libexpat.so.0 to /lib/tls/libc.so.6: normal symbol `malloc' [GLIBC_2.4] この例では全部で38000行
LD_DEBUG=statisticsの例 # LD_DEBUG=statistics ./gtk-hello 574: number of relocations: 547 574: number of relocations from cache: 44 574: number of relative relocations: 14309 574: 574: runtime linker statistics: 574: final number of relocations: 1957 574: final number of relocations from cache: 44 プロセスID
Prelinkの効果 547 44 33 14309 1957 56 prelink無し libのみprelink 全て prelink number of relocations 547 number of relocations from cache 44 33 number of relative relocations 14309 runtime final number of relocations 1957 56 final number of relocations from cache テストプログラムは gtkで画面上にボタンを1個表示するもの。
実行時の物理メモリ消費量の比較 4300 4216 4164 prelink無し libのみprelink 全て prelink VmRSS (KBytes) 4300 4216 4164 -136KB テストプログラムは前頁と同じもの。 cat /proc/PID/status で観測した。 起動時のrelocationの処理が軽くなったのに伴って物理メモリ使用量も 減少している。
簡単なまとめ プラス面 マイナス面 起動時間が短縮される 実行時の物理メモリ消費量も少なくて済む ルートファイルシステム構築時に1工程増える ファイルサイズが少し増加する 大抵のケースではプラス面が勝るであろう。
Prelinkの柔軟性 prelinkされていないライブラリが混じってしまっても起動時間は最適でないけれども実行はできる。このため致命的な問題が起こりにくい。 prelinkしたライブラリを元に戻すこともできる。(undoのための情報を保持している。この情報の削除に関して後述)
PrelinkのTips stripしてからprelinkするほうがファイルサイズが小さくなる。 undo情報を削除してファイルサイズを減らそうと試みた(しかしうまくいかなかった。)
ビルド手順へのprelinkの組込み stripしてからprelinkするほうがファイルサイズが小さくなる。 size: 6,508 size: 9,452 strip 元の実行ファイル こちらが小さい size: 10,511 strip size: 13,699 size: 9,692 prelink 先にstripしたほうが、prelinkのときに付加されるundoのための情報の サイズが小さくて済むため
undo情報を削除してファイルサイズを減らす undo情報は起動時には使用されることが無い。組込みの場合はundoすることはあまりないと思われるため、undo情報は冗長? objcopyコマンドかstripコマンドでundo情報のセクションを削除する $ strip –remove-section=.gnu.prelink_undo <filename> これで減らせるファイルサイズは数百バイト~数キロバイト PCのLinux環境では問題がなかったが、手元にあったARMの環境では 実行ファイルでこれを行うと起動時にsegmentation faultになるものがあった。 ライブラリだけなら問題なかった。
参考文献 “Prelink” http://people.redhat.com/jakub/prelink/prelink.pdf “Linkers & Loaders” オーム社 “BINARY HACKS” オライリージャパン “GNU Development Tools” Wataru Nishida GNU C ライブラリのソース http://www.gnu.org/software/libc/ その他たくさんのWEB検索結果