home ホーム search 検索 -  login ログイン  | reload edit datainfo version cmd icon diff delete  | help ヘルプ

技術/Linux/uClibc/02, "symbol 'stdout': can't resolve symbol in lib" の対処 (v1)

技術/Linux/uClibc/02, "symbol 'stdout': can't resolve symbol in lib" の対処 (v1)

技術 / Linux / uClibc / 02, "symbol 'stdout': can't resolve symbol in lib" の対処 (v1)
id: 948 所有者: msakamoto-sf    作成日: 2011-04-11 14:41:30
カテゴリ: Linux 

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 は無効になっています。

これで解決しない場合は、本記事のサポート範囲外です。

原因

予備知識:

  1. uClibcの動的リンカローダ(ld-uClibc)はsoファイルの "Dynamic Section" 内のハッシュを参照してシンボルの解決と再配置を行う。
  2. このハッシュには昔ながらのsysvスタイル(DT_HASH)と、高速化したGNUハッシュスタイル(DT_GNU_HASH)がある。
  3. どのハッシュスタイルを使うのかは、binutilsのldのオプション、"--hash-style"で指定できる。デフォルトは"sysv"(DT_HASH), 他に"gnu"(DT_GNU_HASH), "both"(両方のスタイルを併存させる)がある。
  4. 上掲の環境では、gcc が collect2, つまりldに渡す "--hash-style" のデフォルト値は "gnu" になっている。
  5. uClibc は LDSO_GNU_HASH_SUPPORT が無効になっていると "--hash-style" オプションは指定せず、toolchain(=gcc)のデフォルトに任せる。また、ld-uClibcはsysvスタイル(DT_HASH)のみを参照してシンボルの解決・再配置を行う。
  6. 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をリビルドすることで解決した場合は、以上の内容が原因として考えられます。

参考:

以下、"--hash-style" の実験とuClibcの関連コードの紹介です。

おまけ:"--hash-style" の実験

とりあえず "--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の関連ソース紹介

原因を特定する時に調査したソースコードを紹介します。ファイルのパスはソースのルート(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() 関連箇所のソース概略:

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()

  • __dl_parse_dynamic_info() に処理を委譲しています。

ldso/include/dl-elf.h : __dl_parse_dynamic_info()

  • インライン関数として定義されています。
  • この中で "Dynamic Section" 中のエントリ、ElfXX_Dyn構造体のエントリをループで辿り、"DT_"で始まる定義値に応じて適宜 dynamic_info にコピーしています。

ここまで到達してようやく以下の推測ができました。

  1. __dl_parse_dynamic_info()で"DT_HASH", "DT_GNU_HASH" が見つからない。
  2. ldso/ldso/dl-hash.c : _dl_add_elf_hash_table() で nbucket が適切に設定されず、0のまま。
  3. 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日を経てようやく歩みを再開できました。うん、つまり 技術/Linux/uClibc/01, "make install_headers" 関連PATCH(uClibc-0.9.31) と今回の問題、対処完了まで4日もかかった訳です。・・・疲れた。



プレーンテキスト形式でダウンロード
現在のバージョン : 1
更新者: msakamoto-sf
更新日: 2011-04-11 17:45:28
md5:53f0e8e923a915ccce9e1ffda91926ce
sha1:1ceed7d316537f3a80008d01d3fde5e90dfe8725
コメント
コメントを投稿するにはログインして下さい。