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 は無効になっています。
これで解決しない場合は、本記事のサポート範囲外です。
予備知識:
上記を踏まえ、LDSO_GNU_HASH_SUPPORTが無効になっていると何が起こるか:
LDSO_GNU_HASH_SUPPORTを有効にしてuClibcをリビルドすることで解決した場合は、以上の内容が原因として考えられます。
参考:
以下、"--hash-style" の実験とuClibcの関連コードの紹介です。
とりあえず "--hash-style" の有無が見た目ではっきり区別できるセクション名とかDynamic Sectionについて確認してみます。
func.c:
int func(int a, int b) { return a + b; }
デフォルト(=GNUハッシュスタイル):
$ 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スタイル:
$ 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)の組み合わせになってます。
最後に両方併存バージョン:
$ 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_" で始まる名前で定義されています。
/* 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-0.9.31)からの相対パスになります。
ちなみに、調査開始時点では動的リンクローダ(ld)がDynamic Sectionのハッシュを参照する仕組みを知りませんでした。
・・・
・・・
・・・
し、知ってたら即効で libuClibc をobjdumpしてGNU_HASHだけでHASHが無いことから、もっと早く、スマートに LDSO_GNU_HASH_SUPPORT のオプションに到達できてたよ!!・・・多分。
ldso/ldso/i386/elfinterp.c : _dl_parse()
ldso/ldso/i386/elfinterp.c : _dl_parse_relocation_information()
ldso/ldso/i386/elfinterp.c : _dl_do_reloc()
ldso/dl-hash.h : _dl_find_hash()
ldso/ldso/dl-hash.c : _dl_lookup_hash()
ところが、調査中はそもそも "_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() 関連箇所のソース概略:
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()
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()
ldso/include/dl-elf.h : __dl_parse_dynamic_info()
ここまで到達してようやく以下の推測ができました。
あとは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日を経てようやく歩みを再開できました。うん、つまり 技術/Linux/uClibc/01, "make install_headers" 関連PATCH(uClibc-0.9.31) と今回の問題、対処完了まで4日もかかった訳です。・・・疲れた。