gcc と -static-libgcc と
Gentoo/DragonFly BSD の基礎部分もだいぶできあがってきて残すは clean up だけ、という感じになってまいりました。
Gentoo には sandbox というものがあります。これは emerge 中に / 環境においたをしないかどうかをチェックしておいたしそうなところを妨害する仕組みです。これを Gentoo/DragonFly でも動かすべく作業していました。
まず sandbox の仕組み。ほとんどのプログラムは libc.so に dynamic にリンクしていて実行時に /lib/libc.so なりからコードを読みこみます。 sandbox では LD_PRELOAD なり LD_LIBRARY_PATH なりを駆使して、読みこまれる libc.so を sandbox のものにしてしまい、おいたをしないかどうかチェックする機構を挟み込んでいます。
すなわち、バイナリが静的にリンクされている場合は sandbox は動作することができません。静的にリンクされているものを見つけると "QA: Static ELF: ./conftest: ./conftest" といった警告が出力されてきます。
Gentoo/DragonFly の場合、 /sbin/mount や /bin/ls やらが static link されていたのでこのQAが大量に出ていました。 そこでこんなパッチをあてて static link しないようにしておきます。
diff --git a/bin/Makefile.inc b/bin/Makefile.inc index 6492a9d..9e6907f 100644 --- a/bin/Makefile.inc +++ b/bin/Makefile.inc @@ -3,5 +3,4 @@ # $DragonFly: src/bin/Makefile.inc,v 1.3 2005/02/06 06:12:32 okumoto Exp $ BINDIR?= /bin -NOSHARED?= YES WARNS?= 6
そうすると今度は
% ldd /bin/cp /bin/cp: libgcc_s.so.1 => /usr/lib/gcc/i686-gentoo-dragonfly2.6/4.4.3/libgcc_s.so.1 (0x2808c000) libc.so.7 => /lib/libc.so.7 (0x28099000)
このように、 libgcc_s.so に依存する結果になってしまいました。 libgcc_s とはなにもの…?
適当なCコードを書いて gcc -v hoge.c としてリンクの様子を見てみます。
/usr/libexec/gcc/i686-gentoo-dragonfly2.6/4.4.3/collect2 --eh-frame-hdr -V -dynamic-linker /libexec/ld-elf.so.2 /usr/lib/gcc/i686-gentoo-dragonfly2.6/4.4.3/../../../crt1.o /usr/lib/gcc/i686-gentoo-dragonfly2.6/4.4.3/../../../crti.o /usr/lib/gcc/i686-gentoo-dragonfly2.6/4.4.3/crtbegin.o -L/usr/lib/gcc/i686-gentoo-dragonfly2.6/4.4.3 -L/usr/lib/gcc/i686-gentoo-dragonfly2.6/4.4.3 -L/usr/lib/gcc/i686-gentoo-dragonfly2.6/4.4.3/../../../../i686-gentoo-dragonfly2.6/lib -L/usr/lib/gcc/i686-gentoo-dragonfly2.6/4.4.3/../../.. /tmp//ccQezMIY.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/i686-gentoo-dragonfly2.6/4.4.3/crtend.o /usr/lib/gcc/i686-gentoo-dragonfly2.6/4.4.3/../../../crtn.o
長いですが大事なのは crt* のパスと "-lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed" の部分です。
% ldd a.out a.out: libgcc_s.so.1 => /usr/lib/gcc/i686-gentoo-dragonfly2.6/4.4.3/libgcc_s.so.1 (0x2808a000) libc.so.7 => /lib/libc.so.7 (0x28097000) % nm a.out 08049f34 d _DYNAMIC 08049ff4 d _GLOBAL_OFFSET_TABLE_ w _Jv_RegisterClasses 08049f2c D __DTOR_END__ 0804a024 A __bss_start w __deregister_frame_info@@GCC_3.0 0804a020 D __dso_handle 0804a01c D __progname w __register_frame_info@@GCC_3.0 0804a024 A _edata 0804a048 A _end 080487e8 T _fini 0804843c T _init U _init_tls U _rtld_call_init 08048580 T _start U atexit 0804a048 A end 0804a044 B environ U exit 080487b0 T main U set_tls_area
結果の a.out を見てみると確かに libgcc_s に依存しています。一体どのシンボルを必要としているのか…。候補としては weak symbol である "__(de)?register_*" と U である "_init_tls" などです。
nm -D /lib/libc.so すると U である "_init_tls" あたりは libc から weak symbol として定義されていることがわかります。
ということで、 __(de)register_frame_info について。
もともとこれはエラー処理に使われる関数です(多分)。 crtbegin や libgcc_eh.a, libgcc_s.so なんかで定義されています。
% nm /usr/lib/gcc/i686-gentoo-dragonfly2.6/4.4.3/crtbegin.o w _Jv_RegisterClasses U __DTOR_END__ w __deregister_frame_info 00000000 D __dso_handle w __register_frame_info % nm /usr/lib/gcc/i686-gentoo-dragonfly2.6/4.4.3/libgcc_eh.a|grep register_frame 00000e50 T __deregister_frame 00000e40 T __deregister_frame_info 00000d60 T __deregister_frame_info_bases 00001150 T __register_frame 00001120 T __register_frame_info 00001090 T __register_frame_info_bases 00001030 T __register_frame_info_table 00000fb0 T __register_frame_info_table_bases 00001060 T __register_frame_table % nm -D /usr/lib/gcc/i686-gentoo-dragonfly2.6/4.4.3/libgcc_s.so|grep register_frame 00008c00 T __deregister_frame 00008bf0 T __deregister_frame_info 00008b10 T __deregister_frame_info_bases 00008f00 T __register_frame 00008ed0 T __register_frame_info 00008e40 T __register_frame_info_bases 00008de0 T __register_frame_info_table 00008d60 T __register_frame_info_table_bases 00008e10 T __register_frame_table
さて、リンクは "...crtbegin.o ... -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed ..." と実行されます。
__(de)register_frame_info についてのリンクが
- crtbegin.o : weak symbol としてリンク
- libgcc.a : シンボルテーブルに __(de)register_frame_info が載っていない
- libgcc_s.so : 通常のシンボルとして定義されているので、これにリンクされる
- libc.so : weak symbol なので無視される
といった具合いに動きます。 "--as-needed" は「必要に応じて」ということです。実際、 Linux なんかでも同様なリンクをしていますが __(de)register_frame が crtbegin に入ってないし、使われてないので libgcc_s.so にリンクされることはないのです。
いくつか、リンクを手動で試してみました。 たとえば "...crtbegin.o ... -lgcc -lgcc_eh --as-needed -lgcc_s --no-as-needed -lc -lgcc -lgcc_eh --as-needed -lgcc_s --no-as-needed ..." として __(de)register_frame_info を先に libgcc_eh.a で解決させようとしたり…。しかし、 *.so で定義されていたらそちらが優先されるのかうまくいきません。残念。
しかたなく、 "-static-libgcc" を常につけるように profile.bashrc に書くことにしました。 これで "...crtbegin.o ... -lgcc -lgcc_eh -lc -lgcc -lgcc_eh ..." というリンクになります。
以下、蛇足
「Linux なんかでも同様なリンクをしていますが __(de)register_frame が crtbegin に入ってないし、使われてないので libgcc_s.so にリンクされることはないのです。」
gcc-4.4.3/gcc/crtstuff.c を見てみると
93 # if !defined(__UCLIBC__) \ 94 || (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ > 2) \ 95 || (__GLIBC__ == 2 && __GLIBC_MINOR__ == 2 && defined(DT_CONFIG))) \ 96 || (__FreeBSD_version >= 700022) 97 # define USE_PT_GNU_EH_FRAME 98 # endif 99 #endif 100 #if defined(EH_FRAME_SECTION_NAME) && !defined(USE_PT_GNU_EH_FRAME) 101 # define USE_EH_FRAME_REGISTRY 102 #endif ... 314 #ifdef USE_EH_FRAME_REGISTRY 315 #ifdef CRT_GET_RFIB_DATA 316 /* If we used the new __register_frame_info_bases interface, 317 make sure that we deregister from the same place. */ 318 if (__deregister_frame_info_bases) 319 __deregister_frame_info_bases (__EH_FRAME_BEGIN__); 320 #else 321 if (__deregister_frame_info) 322 __deregister_frame_info (__EH_FRAME_BEGIN__); 323 #endif 324 #endif ... 346 #ifdef USE_EH_FRAME_REGISTRY 347 static struct object object; 348 #ifdef CRT_GET_RFIB_DATA 349 void *tbase, *dbase; 350 tbase = 0; 351 CRT_GET_RFIB_DATA (dbase); 352 if (__register_frame_info_bases) 353 __register_frame_info_bases (__EH_FRAME_BEGIN__, &object, tbase, dbase); 354 #else 355 if (__register_frame_info) 356 __register_frame_info (__EH_FRAME_BEGIN__, &object); 357 #endif /* CRT_GET_RFIB_DATA */ 358 #endif /* USE_EH_FRAME_REGISTRY */
とあって、最近の Linux や FreeBSD だと 94,95行目にあてはまるので USE_PT_GNU_EH_FRAME が使われ(97)、 USE_EH_FRAME_REGISTRY が使われず(100-102)、 __(de)register_frame_info が使われません(314-324, 346-358)。
gcc のリンクオプション
たとえば、こんな感じで定義されてます。
gcc-4.4.3/gcc/config/i386/cygwin.h
52 #ifdef ENABLE_SHARED_LIBGCC 53 #define SHARED_LIBGCC_SPEC " \ 54 %{static|static-libgcc:-lgcc -lgcc_eh} \ 55 %{!static: \ 56 %{!static-libgcc: \ 57 %{!shared: \ 58 %{!shared-libgcc:-lgcc -lgcc_eh} \ 59 %{shared-libgcc:-lgcc_s -lgcc} \ 60 } \ 61 %{shared:-lgcc_s -lgcc} \ 62 } \ 63 } " 64 #else 65 #define SHARED_LIBGCC_SPEC " -lgcc " 66 #endif 67 68 #undef REAL_LIBGCC_SPEC 69 #define REAL_LIBGCC_SPEC \ 70 "%{mno-cygwin: %{mthreads:-lmingwthrd} -lmingw32} \ 71 " SHARED_LIBGCC_SPEC " \ 72 %{mno-cygwin:-lmoldname -lmingwex -lmsvcrt}"
とはいえ、これは特別な場合で実際には gcc-4.4.3/gcc/gcc.c で
1668 static void 1669 init_gcc_specs (struct obstack *obstack, const char *shared_name, 1670 const char *static_name, const char *eh_name) 1671 { ... 1674 buf = concat ("%{static|static-libgcc:", static_name, " ", eh_name, "}" 1675 "%{!static:%{!static-libgcc:" 1676 #if USE_LD_AS_NEEDED 1677 "%{!shared-libgcc:", 1678 static_name, " --as-needed ", shared_name, " --no-as-needed" 1679 "}" 1680 "%{shared-libgcc:", 1681 shared_name, "%{!shared: ", static_name, "}" 1682 "}" 1683 #else ... 1696 #endif 1697 "}}", NULL); ... 1701 } 1702 #endif /* ENABLE_SHARED_LIBGCC */ ... 1777 if (in_sep && *p == '-' && strncmp (p, "-lgcc", 5) == 0) 1778 { 1779 init_gcc_specs (&obstack, 1780 "-lgcc_s" 1781 #ifdef USE_LIBUNWIND_EXCEPTIONS 1782 " -lunwind" 1783 #endif 1784 , 1785 "-lgcc", 1786 "-lgcc_eh" 1787 #ifdef USE_LIBUNWIND_EXCEPTIONS 1788 # ifdef HAVE_LD_STATIC_DYNAMIC 1789 " %{!static:-Bstatic} -lunwind %{!static:-Bdynamic}" 1790 # else 1791 " -lunwind" 1792 # endif 1793 #endif 1794 ); 1795 1796 p += 5; 1797 in_sep = 0; 1798 }
のように生成され、
%{static|static-libgcc:-lgcc -lgcc_eh} %{!static: %{!static-libgcc: %{!shared-libgcc:-lgcc --as-needed -lgcc_s --no-as-needed} %{shared-libgcc:-lgcc_s %{!shared: -lgcc}}}}
というものになるようです。 たしかに static-libgcc をつけると "-lgcc -lgcc_eh" に、なにもついてないと "-lgcc --as-needed -lgcc_s --no-as-needed" になりそうですね。