2014年09月05日

環境変数でLinuxのglibc動的リンカにログを出力させる

たまには役に立ちそうなネタも書かないとということで、この間社内で話題※になった、glibc 環境の Linux の動的リンカに共有ライブラリのデバッグ時に有益な情報を出力させる方法を紹介します。

※ この Tips は、弊社の PARTNER-AP(JTAG-ICE デバッガではなく、Linux アプリをデバッグするためのアプリデバッガ)案件で役に立った実績のある Tips なので、glibc と共有ライブラリを使用するような、比較的リッチな Linux 環境において有効です。(デバッガとは無関係で、純粋に Linux 環境でのデバッグ機能の紹介となります。)


Linux のアプリが大規模になってくると、非常に大量の共有ライブラリを動的ロードすることとなり、実際にどの共有ライブラリがどんな風にロードされているのか把握が困難になる場合があります。

そのような場合、LD_DEBUG という環境変数に値をセットすることにより、動的リンカに様々な情報を出力させることができます。

今回は、たまたま手元にあったアルティマ社の Helio ボードで付属の ARM Linux がすぐに動いたので、シリアルコンソール(mini USB ケーブル)を繋いで確認環境に使いました。(以下は起動ログの一部です。)
。。。
U-Boot 2013.01.01 (Nov 04 2013 - 23:53:26)

CPU   : Altera SOCFPGA Platform
BOARD : Altera SOCFPGA Cyclone V Board
DRAM:  1 GiB
MMC:   ALTERA DWMMC: 0
。。。
Linux version 3.9.0 (jrucker@altera-linux-jr) (gcc version 4.7.3 20121106 (prerelease) (crosstool-NG linaro-1.13.1-4.7-2012.11-20121123 - Linaro GCC 2012.11) )
今回はとりあえず ls コマンド(/bin/ls)の実行ファイルをターゲットプログラムに使用しました。(あまり面白くは無いですが…。)
(追記: 組み込み Linux で定番ですが、この ls は /bin/busybox のシンボリックリンクでした。/usr/bin/objdump とかの方が依存ライブラリも多くて良かったかもしれません。)

ls がどんな共有ライブラリに依存しているのかを調べるために、ldd コマンドを使おうとしたのですが、この環境には存在しませんでした。こういう場合には、LD_TRACE_LOADED_OBJECTS という環境変数に 1 をセットしてターゲットプログラムを実行すれば、同じ結果が得られます。(参考文献: BINARY HACKS pp.34-35 「ldd で共有ライブラリの依存関係をチェックする」)
root@socfpga:~# which ls
/bin/ls
root@socfpga:~# LD_TRACE_LOADED_OBJECTS=1 ls
        libc.so.6 => /lib/libc.so.6 (0x76ec9000)
        /lib/ld-linux-armhf.so.3 (0x76fab000)
これで依存関係がわかりましたが、これはどのようにして決定されたのでしょうか?それを調べるためには LD_DEBUG 環境変数を使用します。まずは、LD_DEBUG に help という値をセットしてターゲットプログラムを実行すれば、どんな値がセット可能なのかわかります。
root@socfpga:/bin# LD_DEBUG=help ls
Valid options for the LD_DEBUG environment variable are:

  libs        display library search paths
  reloc       display relocation processing
  files       display progress for input file
  symbols     display symbol table processing
  bindings    display information about symbol binding
  versions    display version dependencies
  scopes      display scope information
  all         all previous options combined
  statistics  display relocation statistics
  unused      determined unused DSOs
  help        display this help message and exit

To direct the debugging output into a file instead of standard output
a filename can be specified using the LD_DEBUG_OUTPUT environment variable.
試しに symbols をセットしてみると、大量の情報が表示されたので、とりあえず malloc で grep してみました。(LD_DEBUG の出力は標準エラー出力に出るので、2>&1 でパイプに渡しています。)
root@socfpga:~# LD_DEBUG=symbols ls 2>&1 | grep malloc
      2661:     symbol=__malloc_initialize_hook;  lookup in file=ls [0]
      2661:     symbol=__malloc_initialize_hook;  lookup in file=/lib/libc.so.6 [0]
      2661:     symbol=malloc;  lookup in file=ls [0]
      2661:     symbol=__malloc_hook;  lookup in file=ls [0]
      2661:     symbol=__malloc_hook;  lookup in file=/lib/libc.so.6 [0]
      2661:     symbol=malloc;  lookup in file=ls [0]
      2661:     symbol=malloc;  lookup in file=/lib/libc.so.6 [0]
      2661:     symbol=malloc;  lookup in file=. [0]
      2661:     symbol=malloc;  lookup in file=/lib/libc.so.6 [0]
      2661:     symbol=malloc;  lookup in file=. [0]
      2661:     symbol=malloc;  lookup in file=/lib/libc.so.6 [0]
ダンプなので少し見ずらいですが、malloc というシンボルは /lib/libc.so.6 由来であることがわかります。
この共有ライブラリはどのような順番で検索されたのでしょうか?それを調べるために、今度は libs をセットしてみました。
root@socfpga:~# LD_DEBUG=libs ls
      3479:     find library=libc.so.6 [0]; searching
      3479:      search cache=/etc/ld.so.cache
      3479:      search path=/lib/arm-linux-gnueabihf/tls/v7l/neon/vfp:/lib/arm-linux-gnueabihf/tls/v7l/neon:/lib/arm-linux-gnueabihf/tls/v7l/vfp:/lib/arm-linux-gnueabihf/tls/v7l:/lib/arm-linux-gnueabihf/tls/neon/vfp:/lib/arm-linux-gnueabihf/tls/neon:/lib/arm-linux-gnueabihf/tls/vfp:/lib/arm-linux-gnueabihf/tls:/lib/arm-linux-gnueabihf/v7l/neon/vfp:/lib/arm-linux-gnueabihf/v7l/neon:/lib/arm-linux-gnueabihf/v7l/vfp:/lib/arm-linux-gnueabihf/v7l:/lib/arm-linux-gnueabihf/neon/vfp:/lib/arm-linux-gnueabihf/neon:/lib/arm-linux-gnueabihf/vfp:/lib/arm-linux-gnueabihf:/usr/lib/arm-linux-gnueabihf/tls/v7l/neon/vfp:/usr/lib/arm-linux-gnueabihf/tls/v7l/neon:/usr/lib/arm-linux-gnueabihf/tls/v7l/vfp:/usr/lib/arm-linux-gnueabihf/tls/v7l:/usr/lib/arm-linux-gnueabihf/tls/neon/vfp:/usr/lib/arm-linux-gnueabihf/tls/neon:/usr/lib/arm-linux-gnueabihf/tls/vfp:/usr/lib/arm-linux-gnueabihf/tls:/usr/lib/arm-linux-gnueabihf/v7l/neon/vfp:/usr/lib/arm-linux-gnueabihf/v7l/neon:/usr/lib/arm-linux-gnueabihf/v7l/vfp:/usr/lib/arm-linux-gnueabihf/v7l:/usr/lib/arm-linux-gnueabihf/neon/vfp:/usr/lib/arm-linux-gnueabihf/neon:/usr/lib/arm-linux-gnueabihf/vfp:/usr/lib/arm-linux-gnueabihf:/lib/tls/v7l/neon/vfp:/lib/tls/v7l/neon:/lib/tls/v7l/vfp:/lib/tls/v7l:/lib/tls/neon/vfp:/lib/tls/neon:/lib/tls/vfp:/lib/tls:/lib/v7l/neon/vfp:/lib/v7l/neon:/lib/v7l/vfp:/lib/v7l:/lib/neon/vfp:/lib/neon:/lib/vfp:/lib:/usr/lib/tls/v7l/neon/vfp:/usr/lib/tls/v7l/neon:/usr/lib/tls/v7l/vfp:/usr/lib/tls/v7l:/usr/lib/tls/neon/vfp:/usr/lib/tls/neon:/usr/lib/tls/vfp:/usr/lib/tls:/usr/lib/v7l/neon/vfp:/usr/lib/v7l/neon:/usr/lib/v7l/vfp:/usr/lib/v7l:/usr/lib/neon/vfp:/usr/lib/neon:/usr/lib/vfp:/usr/lib            (system search path)
      3479:       trying file=/lib/arm-linux-gnueabihf/tls/v7l/neon/vfp/libc.so.6
      3479:       trying file=/lib/arm-linux-gnueabihf/tls/v7l/neon/libc.so.6
      3479:       trying file=/lib/arm-linux-gnueabihf/tls/v7l/vfp/libc.so.6
      3479:       trying file=/lib/arm-linux-gnueabihf/tls/v7l/libc.so.6
      3479:       trying file=/lib/arm-linux-gnueabihf/tls/neon/vfp/libc.so.6
(中略)
      3479:       trying file=/lib/tls/libc.so.6
      3479:       trying file=/lib/v7l/neon/vfp/libc.so.6
      3479:       trying file=/lib/v7l/neon/libc.so.6
      3479:       trying file=/lib/v7l/vfp/libc.so.6
      3479:       trying file=/lib/v7l/libc.so.6
      3479:       trying file=/lib/neon/vfp/libc.so.6
      3479:       trying file=/lib/neon/libc.so.6
      3479:       trying file=/lib/vfp/libc.so.6
      3479:       trying file=/lib/libc.so.6
      3479:
      3479:
      3479:     calling init: /lib/libc.so.6
      3479:
      3479:
      3479:     initialize program: ls
      3479:
      3479:
      3479:     transferring control: ls
      3479:
README  altera
      3479:
      3479:     calling fini: . [0]
      3479:
/etc/ld.so.cache 内の探索パスリストを元に順番にトライして行って、最終的には /lib/libc.so.6 が発見されるまでの様子がわかります。
他にも versions でバージョンの制約がどのように解決されたのかを出力するなど、場合によっては便利そうな値がいろいろある感じです。
root@socfpga:~# LD_DEBUG=versions ls
     17622:     checking for version `GLIBC_2.4' in file /lib/libc.so.6 [0] required by file ls [0]
     17622:     checking for version `GLIBC_2.4' in file /lib/ld-linux-armhf.so.3 [0] required by file /lib/libc.so.6 [0]
     17622:     checking for version `GLIBC_PRIVATE' in file /lib/ld-linux-armhf.so.3 [0] required by file /lib/libc.so.6 [0]
     17622:
     17622:     calling init: /lib/libc.so.6
。。。
statistics を大きなプログラムに対して実行すると、こんなに大量のリロケーションが裏で発生しているんだと実感できて面白いかもしれません。
root@socfpga:~# LD_DEBUG=statistics ls
     17690:                      number of relocations: 89
     17690:           number of relocations from cache: 3
     17690:             number of relative relocations: 1207
README  altera
     17690:
     17690:     runtime linker statistics:
     17690:                final number of relocations: 118
     17690:     final number of relocations from cache: 3


トラックバックURL

コメントする

名前
 
  絵文字
 
 
記事検索
最新コメント
アクセスカウンター
  • 今日:
  • 昨日:
  • 累計:

QRコード
QRコード