ldd for Android: lddコマンドのようにライブラリ情報を表示

AndroidにはLinuxのlddコマンドのようにファイルがリンクするシェアードライブラリを表示してくれるコマンドはないようです。ネットで検索してもそれらしい情報が見つかりません。そこで、Androidでも同様のことができるように、以下、試してみました。

[Linxのlddコマンド]
$ ldd /bin/date
	linux-gate.so.1 =>  (0x00b3e000)
	librt.so.1 => /lib/librt.so.1 (0x009b2000)
    (以下、省略)

開発ホスト側のコマンドとしては、arm-eabi-readelfというコマンドがあり、Linuxのreadelfコマンドと同じく-dオプションでシェアードライブラリ名を含むELF dynamic sectionの情報を表示できます。

[Linxホストのarm-eabi-readelfコマンド] ソースはAndroid4.0.3、ターゲットはNexus Sの例です。
$ cd <ソースのルート>/out/target/product/crespo/system/bin
$ <ソースのルート>/prebuilt/linux-x86/toolchain/arm-eabi-4.4.3/bin/arm-eabi-readelf -d date | grep -i shared
 0x00000001 (NEEDED)                     Shared library: [liblog.so]
 0x00000001 (NEEDED)                     Shared library: [libcutils.so]
(以下、省略)

これはファイルに書かれたリンク情報だけなので、ライブラリからさらにリンクされたライブラリなど、すべての必要なライブラリをトレースするには何度もコマンドを実行しなければなりません。それに、システム上で実際にリンクされるライブラリのパスなど、ターゲットのAndroidデバイス上で実行して確認したいことがよくあります。
ところで、Linuxのlddコマンドはダイナミックリンカld.so(ld-linux.so)を呼び出すシェルスクリプトです。ld-linux.soはコマンド単体として実行でき、また環境変数の設定で、ライブラリ情報を表示してくれます。

$ /lib/ld-linux.so.2 --list /bin/date
	linux-gate.so.1 =>  (0x00ac8000)
	librt.so.1 => /lib/librt.so.1 (0x009b2000)
   (以下、省略)
$ export LD_TRACE_LOADED_OBJECTS=1
$ date
	linux-gate.so.1 =>  (0x00298000)
	librt.so.1 => /lib/librt.so.1 (0x009b2000)
   (以下、省略)
$ unset LD_TRACE_LOADED_OBJECTS

そこで、Androidのダイナミックリンカ/system/bin/linkerのソースに変更を加えて、環境変数の設定によって、Linuxのld-linux.soのようにライブラリ情報を表示するようにしてみました。

$ export LD_LIBRARY_LIST=1       ← 私が適当に名前を付けた環境変数 (lddのLD_TRACE_LOADED_OBJECTSと同じ意味。これ名前が長い。)                       
$ date
	liblog.so => /system/lib/liblog.so
	libc.so => /system/lib/libc.so
    (以下、省略)
$ bluetoothd                                                   
	libbluetoothd.so => /system/lib/libbluetoothd.so
	libbluetooth.so => not found    ← シェアードライブラリが見つからない場合
	libbtio.so => /system/lib/libbtio.so
    (以下、省略)
$ unset LD_LIBRARY_LIST  
$ date
Wed Jun  6 14:33:18 GMT 2012

(ところで、LD_LIBRARY_PATHの値は、ベンダー/デバイスによって少し違っています。)
 Nexus S: 
 # grep LD_LIBRARY_PATH /init.rc                                
     export LD_LIBRARY_PATH /vendor/lib:/system/lib
 htc EVO WiMAX, Libero 003Z:
 # grep LD_LIBRARY_PATH /init.rc
     export LD_LIBRARY_PATH /system/lib

結構便利です。ただ、ダイナミックリンカ自体を入れ替えるので、もしリンカが動かないとほとんどのプログラムは実行できなくなってしまうので、あまり他人にはすすめられませんが、報告です。(スタティックリンクしたBusyBoxを用意しておけばこのコマンドで元に戻せるので大丈夫ですが。)
近々、/system/bin/linkerとは別の単体のコマンドとして実行できるように作って、またブログにアップしようと思っています。→ こちら (7/23/2012)

以下、/system/bin/linkerのソースの変更箇所です。
備考:
・printf(), putc()など、内部でmalloc()を呼び出す関数は使えないようです。次のようなエラーが出ます。 
  "ERROR: malloc called from the dynamic linker!"
 なので表示にはwrite()システムコールを使いました。
・ダイナミックリンカのソースをコンパイルする時も、ソース全体のコンパイルと同じ要領です。→(1)(2)(3)
 envsetup.shによって定義された関数mmを実行します。
  $ cd <ソースのルート>
  $ . build/envsetup.sh
  $ lunch
  $ cd bionic/linker
  $ vi linker.c
  $ help
  .................
  - mm: Builds all of the modules in the current directory.
  .................
  $ mm

[変更箇所]

$ diff linker.c linker.c.orig
96,97d95
< void print_library_list(const char *name, const char **path);
< void print_library_notfound(const char *name);
641,642d638   ← open_library()関数内にライブラリ情報の表示関数を追加
<         {
<             print_library_list(name, path);
644d639
<         }   
653,654d647   ← open_library()関数内にライブラリ情報の表示関数を追加
<         {
<             print_library_list(name, path);
656d648
<         }
659,660d650   ← open_library()関数内にライブラリ情報の”not found”の表示関数を追加
<     print_library_notfound(name);
< 
1219d1208
< 
1295,1301c1284,1286   ← reloc_library()関数内でライブラリが見つからない場合のエラー終了を回避(環境変数LD_LIBRARY_LISTが設定されていた場合)
< 
<                 const char* lib_list = NULL;
<                 if ((lib_list = linker_env_get("LD_LIBRARY_LIST")) == NULL){
<                   if (ELF32_ST_BIND(s->st_info) != STB_WEAK) {
<                       DL_ERR("%5d cannot locate '%s'...\n", pid, sym_name);
<                       return -1;
<                   }
---
>                 if (ELF32_ST_BIND(s->st_info) != STB_WEAK) {
>                     DL_ERR("%5d cannot locate '%s'...\n", pid, sym_name);
>                     return -1;
1933,1940c1918,1923    ← link_image()関数内でライブラリが見つからない場合のエラー終了を回避(環境変数LD_LIBRARY_LISTが設定されていた場合)
<             const char* lib_list = NULL;
<             if ((lib_list = linker_env_get("LD_LIBRARY_LIST")) == NULL){
<                if(lsi == 0) {
<                    strlcpy(tmp_err_buf, linker_get_error(), sizeof(tmp_err_buf));
<                    DL_ERR("%5d could not load needed library '%s' for '%s' (%s)",
<                           pid, ldpreload_names[i], si->name, tmp_err_buf);
<                    goto fail;
<                }
---
>             if(lsi == 0) {
>                 strlcpy(tmp_err_buf, linker_get_error(), sizeof(tmp_err_buf));
>                 DL_ERR("%5d could not load needed library '%s' for '%s' (%s)",
>                        pid, ldpreload_names[i], si->name, tmp_err_buf);
>                 goto fail;
>             }
1943d1925
<             }
1951,1958c1933,1938    ← link_image()関数内でライブラリが見つからない場合のエラー終了を回避(環境変数LD_LIBRARY_LISTが設定されていた場合)
<             const char* lib_list = NULL;
<             if ((lib_list = linker_env_get("LD_LIBRARY_LIST")) == NULL){
<                if(lsi == 0) {
<                    strlcpy(tmp_err_buf, linker_get_error(), sizeof(tmp_err_buf));
<                    DL_ERR("%5d could not load needed library '%s' for '%s' (%s)",
<                           pid, si->strtab + d[1], si->name, tmp_err_buf);
<                    goto fail;
<                }
---
>             if(lsi == 0) {
>                 strlcpy(tmp_err_buf, linker_get_error(), sizeof(tmp_err_buf));
>                 DL_ERR("%5d could not load needed library '%s' for '%s' (%s)",
>                        pid, si->strtab + d[1], si->name, tmp_err_buf);
>                 goto fail;
>             }
1968d1947
<             }
2274,2277d2252    ← __linker_init()関数の最後で、コマンドを実行せずに終了(環境変数LD_LIBRARY_LISTが設定されていた場合)
< 
<     const char* lib_list = NULL;
<     if ((lib_list = linker_env_get("LD_LIBRARY_LIST")) != NULL) exit(0);
<     
2280,2302d2254    ← ライブラリ表示関数を追加(環境変数LD_LIBRARY_LISTが設定されていた場合にライブラリ情報を表示)
< 
< void print_library_list(const char *name, const char **path) {
<     const char* lib_list = NULL;
<     if ((lib_list = linker_env_get("LD_LIBRARY_LIST")) != NULL){
<        write(1, "\t", 1);
<        write(1, name, strlen(name));
<        write(1, " => ", 4);
<        write(1, *path, strlen(*path));
<        write(1, "/", 1);
<        write(1, name, strlen(name));
<        write(1, "\n", 1);
<     }
< }
< 
< void print_library_notfound(const char *name) {
<     const char* lib_list = NULL;
<     if ((lib_list = linker_env_get("LD_LIBRARY_LIST")) != NULL){
<        write(1, "\t", 1);
<        write(1, name, strlen(name));
<        char str[] = " => not found\n";
<        write(1, str, strlen(str));
<     }
< }