#navi_header|技術| uClibc-0.9.31を動的リンクしたプログラムの実行時に symbol 'stdout': can't resolve symbol in lib '.../libc.so.0' が発生してしまう場合の対処法メモ。 環境: CentOS 5.5 (x86, 32bit) gcc-4.1.2-48.el5 libgcc-4.1.2-48.el5 binutils-2.17.50.0.6-14.el5 uClibc-0.9.31 ''Buildrootは使用せず、ホストマシン(=CentOS)のgccをそのまま使用しています。'' Buildrootを使用していてなおかつ上記エラーが発生してしまうケースは本記事の対象外です。 * 結論 古いuClibcを参照していないかなど、基本的な各種ディレクトリ設定を確認して下さい。 また対象プログラムの".interp"セクションが正しいローダを指しているか確認して下さい。 確認例: $ readelf -x .interp foo 上記を確認した上で、 LDSO_GNU_HASH_SUPPORT を有効にして uClibc をリビルドして下さい。 General Library Settings ---> Enable GNU hash style support "make defconfig" で設定した場合、LDSO_GNU_HASH_SUPPORT は無効になっています。 これで解決しない場合は、本記事のサポート範囲外です。 * 原因 予備知識: + uClibcの動的リンカローダ(ld-uClibc)はsoファイルの "Dynamic Section" 内のハッシュを参照してシンボルの解決と再配置を行う。 + このハッシュには昔ながらのsysvスタイル(DT_HASH)と、高速化したGNUハッシュスタイル(DT_GNU_HASH)がある。 + どのハッシュスタイルを使うのかは、binutilsのldのオプション、"--hash-style"で指定できる。デフォルトは"sysv"(DT_HASH), 他に"gnu"(DT_GNU_HASH), "both"(両方のスタイルを併存させる)がある。 + 上掲の環境では、 ''gcc が collect2, つまりldに渡す "--hash-style" のデフォルト値は "gnu" になっている。'' + uClibc は LDSO_GNU_HASH_SUPPORT が無効になっていると "--hash-style" オプションは指定せず、toolchain(=gcc)のデフォルトに任せる。また、ld-uClibcはsysvスタイル(DT_HASH)のみを参照してシンボルの解決・再配置を行う。 + uClibc は LDSO_GNU_HASH_SUPPORT が有効になっていると "--hash-style=gnu" を指定する。また、ld-uClibcはGNUハッシュスタイル(DT_GNU_HASH)またはsysvスタイル(DT_HASH)を参照してシンボルの解決・再配置を行う。 上記を踏まえ、LDSO_GNU_HASH_SUPPORTが無効になっていると何が起こるか: - uClibcのライブラリは gcc のデフォルト、つまり "--hash-style=gnu" でビルドされます。 - よって例えばlibc.so(=libuClibc)にはGNUハッシュスタイルのみが埋めこまれ、sysvスタイルのハッシュは埋めこまれません。 - ところがld-uClibcによるシンボルの解決・再配置ではsysvスタイルのハッシュだけしか参照しません(LDSO_GNU_HASH_SUPPORTが無効になっているため)。 - → これにより、libc.soをロードしてもsysvスタイルのハッシュが見つからず、エラーになり、問題となっているメッセージを出力して終了します。 LDSO_GNU_HASH_SUPPORTを有効にしてuClibcをリビルドすることで解決した場合は、以上の内容が原因として考えられます。 参考: - DT_GNU_HASH で高速化:日々のあしあと:So-netブログ -- http://daily-note.blog.so-net.ne.jp/2007-04-25 - Jakub Jelinek - [PATCH] DT_GNU_HASH: ~ 50% dynamic linking improvement -- http://sourceware.org/ml/binutils/2006-06/msg00418.html - Re: Detecting DT_GNU_HASH -- http://linux.derkeiler.com/Newsgroups/comp.os.linux.development.system/2007-02/msg00102.html 以下、"--hash-style" の実験とuClibcの関連コードの紹介です。 #more|| * おまけ:"--hash-style" の実験 とりあえず "--hash-style" の有無が見た目ではっきり区別できるセクション名とかDynamic Sectionについて確認してみます。 func.c: #code|c|> int func(int a, int b) { return a + b; } ||< デフォルト(=GNUハッシュスタイル): #pre||> $ gcc -shared -fPIC -o func_hash_gnu.so func.c -v ... /usr/libexec/gcc/i386-redhat-linux/4.1.2/collect2 (...) --hash-style=gnu ... $ objdump -p func_hash_gnu.so ... Dynamic Section: NEEDED libc.so.6 INIT 0x268 FINI 0x3e4 GNU_HASH 0xb4 # "GNU_HASH"だけで、"HASH"は無し。 ... $ objdump -h func_hash_gnu.so ... Sections: Idx Name Size VMA LMA File off Algn 0 .gnu.hash 0000003c 000000b4 000000b4 000000b4 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 1 .dynsym 000000a0 000000f0 000000f0 000000f0 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA ... ||< このように、".gnu.hash"セクション + GNU_HASH(Dynamic Section)の組み合わせになってます。 続いてsysvスタイル: #pre||> $ gcc -shared -fPIC -o func_hash_sysv.so func.c -v -Wl,--hash-style=sysv ... /usr/libexec/gcc/i386-redhat-linux/4.1.2/collect2 ... --hash-style=gnu # これはgcc側で自動的に追加したオプション ... --hash-style=sysv # こちらが "-Wl," で指定したオプションで、こちらで上書き。 ... $ objdump -p func_hash_sysv.so ... Dynamic Section: NEEDED libc.so.6 INIT 0x268 FINI 0x3e4 HASH 0xb4 ... $ objdump -h func_hash_sysv.so ... Sections: Idx Name Size VMA LMA File off Algn 0 .hash 0000003c 000000b4 000000b4 000000b4 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 1 .dynsym 000000a0 000000f0 000000f0 000000f0 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA ... ||< このように、".hash"セクション + HASH(Dynamic Section)の組み合わせになってます。 最後に両方併存バージョン: #pre||> $ gcc -shared -fPIC -o func_hash_both.so func.c -v -Wl,--hash-style=both ... /usr/libexec/gcc/i386-redhat-linux/4.1.2/collect2 ... --hash-style=gnu # これはgcc側で自動的に追加したオプション ... --hash-style=both # こちらが "-Wl," で指定したオプションで、こちらで上書き。 ... $ objdump -p func_hash_both.so ... Dynamic Section: NEEDED libc.so.6 INIT 0x2a4 FINI 0x424 HASH 0xb4 GNU_HASH 0xf0 STRTAB 0x1cc ... $ objdump -h func_hash_both.so ... Sections: Idx Name Size VMA LMA File off Algn 0 .hash 0000003c 000000b4 000000b4 000000b4 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 1 .gnu.hash 0000003c 000000f0 000000f0 000000f0 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 2 .dynsym 000000a0 0000012c 0000012c 0000012c 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA ||< ".hash"セクション + HASH(Dynamic Section)と".gnu.hash"セクション + GNU_HASH(Dynamic Section)の両方が存在します。 Dynamic Sectionの名前ですが、 "/usr/include/elf.h" に "DT_" で始まる名前で定義されています。 #pre||> /* Dynamic section entry. */ typedef struct { ... } Elf32_Dyn; ... /* Legal values for d_tag (dynamic entry type). */ ... #define DT_HASH 4 /* Address of symbol hash table */ ... #define DT_GNU_HASH 0x6ffffef5 /* GNU-style hash table. */ ||< DT_GNU_HASHですが、"linux/elf.h"など他のelf.hには定義されていません。"/usr/include/elf.h"だけのようです。 * おまけ:uClibcの関連ソース紹介 原因を特定する時に調査したソースコードを紹介します。ファイルのパスはソースのルート(uClibc-0.9.31)からの相対パスになります。 ちなみに、調査開始時点では ''動的リンクローダ(ld)がDynamic Sectionのハッシュを参照する仕組みを知りませんでした。'' ・・・ ・・・ ・・・ し、知ってたら即効で libuClibc をobjdumpしてGNU_HASHだけでHASHが無いことから、もっと早く、スマートに LDSO_GNU_HASH_SUPPORT のオプションに到達できてたよ!!・・・多分。 ldso/ldso/i386/elfinterp.c : _dl_parse() - この中で問題となっている "can't resolve..."のメッセージを出力しています。 - 引数で渡ってきたcallback関数 reloc_fnc() の戻り値が1以上の場合が問題となっています。 ldso/ldso/i386/elfinterp.c : _dl_parse_relocation_information() - 今回のケースではこの関数から "_dl_parse()" が呼ばれ、その戻り値を返しています。 - "reloc_fnc()"に相当する引数には _dl_do_reloc() のアドレスを渡しています。 ldso/ldso/i386/elfinterp.c : _dl_do_reloc() - この中で1以上をreturnするのは _dl_find_hash() で妥当なシンボルアドレスを見つけられなかった場合です。 ldso/dl-hash.h : _dl_find_hash() - インライン関数として定義されており、_dl_lookup_hash()に処理を委譲しています。 ldso/ldso/dl-hash.c : _dl_lookup_hash() - この中でロードしたELFファイルの中のDynamic Sectionからハッシュを取得し、シンボルを解決する。 -- LDSO_GNU_HASH_SUPPORT有効なら _dl_lookup_gnu_hash() でGNUハッシュを使う。 -- LDSO_GNU_HASH_SUPPORT無効なら _dl_lookup_sysv_hash() でsysvハッシュを使う。 ところが、調査中はそもそも "_dl_lookup_{gnu|sysv}_hash()" の呼び出しまで到達しませんでした。 ローダされたどのELFファイルも、 /* If the hash table is empty there is nothing to do here. */ if (tpnt->nbucket == 0) continue; ここでcontinueしてしまうのです。 tpntは "struct elf_resolve" のポインタで、elf_resolve構造体は dl-hash.h で定義されています。 この "nbucket" メンバが設定されるのは同じく dl-hash.c 内の_dl_add_elf_hash_table()です。 ldso/ldso/dl-hash.c : _dl_add_elf_hash_table() 関連箇所のソース概略: #pre||> struct elf_resolve *_dl_add_elf_hash_table(const char *libname, DL_LOADADDR_TYPE loadaddr, unsigned long *dynamic_info, unsigned long dynamic_addr, attribute_unused unsigned long dynamic_size) { ... #ifdef __LDSO_GNU_HASH_SUPPORT__ if (dynamic_info[DT_GNU_HASH_IDX] != 0) { tpnt->nbucket = ...; } else /* Fall using old SysV hash table if GNU hash is not present */ #endif if (dynamic_info[DT_HASH] != 0) { tpnt->nbucket = ...; } ||< 調査時点では LDSO_GNU_HASH_SUPPORT の存在すら気づかず、設定も "make defconfig" でしたので当然無効です。 ということは if (dynamic_info[DT_HASH] != 0) { これがfalseで評価されたと思われます。つまり dynamic_info[DT_HASH] == 0 になっているものと思われます。 ということで、今度は _dl_add_elf_hash_table() を呼ぶところを探します。 すると dl-elf.c の _dl_load_elf_shared_library() 内で呼ばれているようです。関数自体は長いですが、dynamic_infoの初期化に注目すると次のような流れになります。 ldso/ldso/dl-elf.c : _dl_load_elf_shared_library() #pre||> struct elf_resolve *_dl_load_elf_shared_library( int secure, struct dyn_elf **rpnt, char *libname) { ... unsigned long dynamic_info[DYNAMIC_SIZE]; ... _dl_memset(dynamic_info, 0, sizeof(dynamic_info)); _dl_parse_dynamic_info(dpnt, dynamic_info, NULL, lib_loadaddr); ... tpnt = _dl_add_elf_hash_table( libname, lib_loadaddr, dynamic_info,dynamic_addr, 0); ... ||< ldso/ldso/dl-elf.c : _dl_parse_dynamic_info() - __dl_parse_dynamic_info() に処理を委譲しています。 ldso/include/dl-elf.h : __dl_parse_dynamic_info() - インライン関数として定義されています。 - この中で "Dynamic Section" 中のエントリ、ElfXX_Dyn構造体のエントリをループで辿り、"DT_"で始まる定義値に応じて適宜 dynamic_info にコピーしています。 ここまで到達してようやく以下の推測ができました。 + __dl_parse_dynamic_info()で"DT_HASH", "DT_GNU_HASH" が見つからない。 + ldso/ldso/dl-hash.c : _dl_add_elf_hash_table() で nbucket が適切に設定されず、0のまま。 + ldso/ldso/dl-hash.c : _dl_lookup_hash() で sysv/gnuハッシュのlookupまで到達しない。 あとはGoogleで検索かけたりして "--hash-style" にたどり着き、Makefile.inなどを検索して LDSO_GNU_HASH_SUPPORT による"--hash-style"オプションの切り替え、さらに折り返しでこれまで見てきたソースコードの #ifdef __LDSO_GNU_HASH_SUPPORT__ ... #endif の意味が判明しました。 最後に実際に"make menuconfig"で LDSO_GNU_HASH_SUPPORT を有効化してリビルド、動作確認してようやく、正常に動作した次第。 以上、uClibcで最初に躓いてから4日を経てようやく歩みを再開できました。うん、つまり [[945]] と今回の問題、対処完了まで4日もかかった訳です。・・・疲れた。 #navi_footer|技術|