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 */

とあって、最近の LinuxFreeBSD だと 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" になりそうですね。