リンカーフラグ -E の意味と使い方 - 2020-09-07 - ククログ

ククログ

株式会社クリアコード > ククログ > リンカーフラグ -E の意味と使い方

リンカーフラグ -E の意味と使い方

プログラムをビルドする際には、色々なオプションをつけて実行することが多いですが、先日 -lluajit-5.1 -Wl,-E というオプションを見ました。 この、-Wl,-E というオプションがなんなのかわからなかったため、調査しました。この記事ではその調査結果を記載しています。

調査対象はコマンドのオプションなので、まずは、コマンドのマニュアルを見てみましょう。 このオプションを使ってビルドするときに使ったコマンドは gcc だったので、gcc のマニュアルを確認しました。 私の環境(Debian GNU/Linux 10.5)のマニュアルでは、 -Wl,-E というオプションは以下のように説明されていました。

       -Wl,option
              オプション option をリンカに渡します。option がコンマを含む場合は、それらのコンマで複数のオプションとして分割されます。

つまり、-Wl,-Eは、-Wl,optionというオプションでoptionの部分にリンカーへのオプションを指定します。 したがって、-Wl,-Eはリンカーに-Eというオプションを渡すという意味になります。

ということで、次は、リンカーのマニュアルを参照してみましょう。 このときのリンカーは、GNU ld を使っていたので、GNU ld のマニュアルを参照します。

私の環境(Debian GNU/Linux 10.5)のマニュアルでは、 -E オプションは以下のように説明されていました。 (英語のマニュアルのほうが情報が多かったので、英語のマニュアルを参照しました。)

       -E
       --export-dynamic
       --no-export-dynamic
           When creating a dynamically linked executable, using the -E option or the --export-dynamic option causes the linker to add all symbols to the
           dynamic symbol table.  The dynamic symbol table is the set of symbols which are visible from dynamic objects at run time.

           If you do not use either of these options (or use the --no-export-dynamic option to restore the default behavior), the dynamic symbol table will
           normally contain only those symbols which are referenced by some dynamic object mentioned in the link.

           If you use "dlopen" to load a dynamic object which needs to refer back to the symbols defined by the program, rather than some other dynamic
           object, then you will probably need to use this option when linking the program itself.

           You can also use the dynamic list to control what symbols should be added to the dynamic symbol table if the output format supports it.  See the
           description of --dynamic-list.

           Note that this option is specific to ELF targeted ports.  PE targets support a similar function to export all symbols from a DLL or EXE; see the
           description of --export-all-symbols below.

注目するのは以下の説明です。

When creating a dynamically linked executable, using the -E option or the --export-dynamic option causes the linker to add all symbols to the dynamic symbol table.
The dynamic symbol table is the set of symbols which are visible from dynamic objects at run time.

(動的にリンクされた実行ファイルを作成する時に、すべてのシンボルを動的シンボルテーブルに加えます。
動的シンボルテーブルは、実行時に動的オブジェクトから見えるシンボルのテーブルです。)


If you use "dlopen" to load a dynamic object which needs to refer back to the symbols defined by the program, rather than some other dynamic object,
then you will probably need to use this option when linking the program itself.

(他の動的オブジェクトではなく、そのプログラムで定義されているシンボルを参照し返す必要のある動的オブジェクトを
dlopen で ロードする場合は、おそらくプログラム自身をリンクする時にこのオプションを 使う必要があるでしょう。)

あまり、ピンときませんね! なので、実際に小さなプログラムを作って実験してみましょう。


では、「他の動的オブジェクトではなく、そのプログラムで定義されているシンボルを参照し返す必要のある動的オブジェクト」を考えてみましょう。 これは、例えば以下のようなケースです。

まず、extendable-programというプログラムがあるとします。 このプログラム用のプラグインとしてextendable-program-plugin.soという動的ライブラリーがあるとします。

以下のように、extendable-program-plugin.soextendable-programに動的にロードして使われることを前提としています。

画像の説明

そして、extendable-program-plugin.soは、extendable-programのバージョンを取得する必要があるとします。 (例えば、extendable-programのバージョンによって提供する機能を切り替えたりするのに使います。)

バージョンの取得は、extendable-programに自身のバージョンを返す関数があり、extendable-program-plugin.soからそれを呼び出すことで実現します。

このときに、-Eオプションをつけてextendable-programがビルドされていると、以下のように、extendable-programのシンボルが動的シンボルテーブルに登録されるので、extendable-program-plugin.soからextendable-programのシンボルが参照できるようになります。

画像の説明

では、実際にプログラムを作って上記の例を実験してみましょう。 実験用のプログラムは以下の通りです。

extendable-program

  • extendable-program.h

    #pragma once
    
    typedef void (*ep_plugin_init_func)(void);
    
    extern int ep_get_major_version(void);
    extern int ep_get_minor_version(void);
    extern int ep_get_micro_version(void);
    
  • extendable-program.c

    #include <stdio.h>
    #include <stdlib.h>
    #include <dlfcn.h>
    
    #include "extendable-program.h"
    
    enum Verion {
      MAJOR = 1,
      MINOR = 0,
      MICRO = 2
    };
    
    int
    ep_get_major_version(void)
    {
      return MAJOR;
    }
    
    int
    ep_get_minor_version(void)
    {
      return MINOR;
    }
    
    int
    ep_get_micro_version(void)
    {
      return MICRO;
    }
    
    int
    main(int argc, char **argv)
    {
      void *plugin = dlopen("./extendable-program-plugin.so", RTLD_NOW | RTLD_LOCAL);
      printf("plugin: %p\n", plugin);
      if (!plugin) {
        return EXIT_FAILURE;
      }
    
      ep_plugin_init_func ep_plugin_init = dlsym(plugin, "ep_plugin_init");
      printf("ep_plugin_init: %p\n", ep_plugin_init);
      if (!ep_plugin_init) {
        dlclose(plugin);
        return EXIT_FAILURE;
      }
    
      ep_plugin_init();
      dlclose(plugin);
    
      return EXIT_SUCCESS;
    }
    

extendable-program-plugin

  • extendable-program-plugin.c

    #include <stdio.h>
    
    #include "extendable-program.h"
    
    extern void ep_plugin_init(void);
    
    void
    ep_plugin_init(void)
    {
      printf("ep-plugin-init: %d.%d.%d\n",
             ep_get_major_version(),
             ep_get_minor_version(),
             ep_get_micro_version());
    }
    

Makefile

all: extendable-program-with-E
all: extendable-program-without-E
all: extendable-program-plugin.so

clean:
	rm -f extendable-program-with-E
	rm -f extendable-program-without-E
	rm -f extendable-program-plugin.so

extendable-program-with-E: extendable-program.c extendable-program.h
	$(CC) -o $@ -Wl,-E extendable-program.c -ldl

extendable-program-without-E: extendable-program.c extendable-program.h
	$(CC) -o $@ extendable-program.c -ldl

extendable-program-plugin.so: extendable-program-plugin.c extendable-program.h
	$(CC) -shared -o $@ extendable-program-plugin.c

以上が実験用のプログラムです。 さっそく実行してみましょう。

$ make
cc -o extendable-program-with-E -Wl,-E extendable-program.c -ldl
cc -o extendable-program-without-E extendable-program.c -ldl
cc -shared -o extendable-program-plugin.so extendable-program-plugin.c

$ ./extendable-program-without-E
plugin: (nil)

$ ./extendable-program-with-E
plugin: 0x55eead4df290
ep_plugin_init: 0x7f2d5f4a4135
ep-plugin-init: 1.0.2

-Eをつけずにビルドしたプログラムextendable-program-without-Eでは、plugin: (nil)となっていて、extendable-program-plugin.soのロードに失敗しています。 (dlopenのオプションにRTLD_NOWが指定されているため、extendable-program-plugin.so内で参照しているシンボルが解決できない場合はdlopenが失敗します。したがって、これはextendable-program-plugin.soで使用しているシンボルが解決できなかったことになります。)

つまり、-Eオプションをつけないと以下のような状態になります。

画像の説明

-Eオプションの説明には、「全てのシンボルを動的シンボルテーブルに追加する」と書かれていました。 つまり、-Eオプションをつけることで、extendable-programのシンボルが全て動的シンボルテーブルに追加され、extendable-program-pluginから参照できるようになります。

実際に、-Eオプションをつけてビルドしたextendable-program-with-Eでは、extendable-program-plugin.soがロードされ正常にextendable-programのバージョンが取得できていることが確認できます。

このように、実行プログラム側で提供しているシンボルを動的ライブラリー側から参照する必要がある場合に-Eオプションが必要だということがわかりました。

-Eオプションの応用

-Eオプションは「全てのシンボルを動的シンボルテーブルに追加する」とマニュアルに記載されていました。これは、静的ライブラリーをリンクして使われることを想定しているLuaJITのようなプログラムにも応用できます。

どう応用できるのかを、先程の実験に使用したプログラムを改造して説明します。 まず、新しく静的ライブラリーlibadd.aを追加します。これは、2値の整数を可算する関数(add_int())と2値の実数を加算する関数(add_double())を提供しています。

今回の例ではextendable-programはこのlibadd.aを静的にリンクしていることを前提とし、libadd.aで提供する関数をextendable-program-plugin.soからも使うこととします。

画像の説明

つまり、以下のような実装になります。

libadd.a

  • add.c

    int add_int(int a, int b);
    int add_int(int a, int b) {
      return a + b;
    }
    
    double add_double(double a, double b);
    double add_double(double a, double b) {
      return a + b;
    }
    

extendable-program

  • extendable-program.h

    #pragma once
    
    typedef void (*ep_plugin_init_func)(void);
    typedef void (*ep_plugin_aggregate_func)(void);
    
    extern int ep_get_major_version(void);
    extern int ep_get_minor_version(void);
    extern int ep_get_micro_version(void);
    
    extern int add_int(int a, int b);
    

extendable-program

  • extendable-program.c

    #include <stdio.h>
    #include <stdlib.h>
    #include <dlfcn.h>
    
    #include "extendable-program.h"
    
    enum Verion {
      MAJOR = 1,
      MINOR = 0,
      MICRO = 2
    };
    
    int
    ep_get_major_version(void)
    {
      return MAJOR;
    }
    
    int
    ep_get_minor_version(void)
    {
      return MINOR;
    }
    
    int
    ep_get_micro_version(void)
    {
      return MICRO;
    }
    
    int
    main(int argc, char **argv)
    {
      int sum = add_int(3, 4);
      printf("sum=%d\n", sum);
    
      void *plugin = dlopen("./extendable-program-plugin.so", RTLD_NOW | RTLD_LOCAL);
      printf("plugin: %p\n", plugin);
      if (!plugin) {
        return EXIT_FAILURE;
      }
    
      ep_plugin_init_func ep_plugin_init = dlsym(plugin, "ep_plugin_init");
      printf("ep_plugin_init: %p\n", ep_plugin_init);
      if (!ep_plugin_init) {
        dlclose(plugin);
        return EXIT_FAILURE;
      }
    
      ep_plugin_aggregate_func ep_plugin_aggregate = dlsym(plugin, "ep_plugin_aggregate");
      printf("ep_plugin_aggregate: %p\n", ep_plugin_aggregate);
      if (!ep_plugin_aggregate) {
        dlclose(plugin);
        return EXIT_FAILURE;
      }
    
      ep_plugin_init();
      ep_plugin_aggregate();
      dlclose(plugin);
    
      return EXIT_SUCCESS;
    }
    

extendable-program-plugin

  • extendable-program-plugin.c

    #include <stdio.h>
    
    #include "extendable-program.h"
    
    extern void ep_plugin_init(void);
    extern double add_double(double a, double b);
    extern void ep_plugin_aggregate(void);
    
    void
    ep_plugin_init(void)
    {
      printf("ep-plugin-init: %d.%d.%d\n",
             ep_get_major_version(),
             ep_get_minor_version(),
             ep_get_micro_version());
    }
    
    void ep_plugin_aggregate(void) {
      printf("ep-plugin-aggregate: %g\n",
             add_double(1.5, 2.2));
    }
    

Makefile

all: libadd.a
all: extendable-program-plugin.so
all: extendable-program-with-E
all: extendable-program-without-E

clean:
	rm -f extendable-program-with-E
	rm -f extendable-program-without-E
	rm -f extendable-program-plugin.so
	rm -f libadd.a

libadd.a: add.c
	$(CC) -c add.c && ar rcs $@ add.o

extendable-program-with-E: extendable-program.c extendable-program.h
	$(CC) -o $@ -Wl,-E extendable-program.c libadd.a -ldl

extendable-program-without-E: extendable-program.c extendable-program.h
	$(CC) -o $@ extendable-program.c libadd.a -ldl

extendable-program-plugin.so: extendable-program-plugin.c extendable-program.h
	$(CC) -shared -o $@ extendable-program-plugin.c

では、さっそく実行してみましょう。

$ make
cc -c add.c && ar rcs libadd.a add.o
cc -shared -o extendable-program-plugin.so extendable-program-plugin.c
cc -o extendable-program-with-E -Wl,-E extendable-program.c ./libadd.a -ldl
cc -o extendable-program-without-E extendable-program.c ./libadd.a -ldl

$ ./extendable-program-without-E
sum=7
plugin: (nil)

$ ./extendable-program-with-E
sum=7
plugin: 0x55c12f1636a0
ep_plugin_init: 0x7f800c18c145
ep_plugin_aggregate: 0x7f800c18c17e
ep-plugin-init: 1.0.2
ep-plugin-aggregate: 3.7

-Eをつけずにビルドしたプログラムでは、sum=7と出力されているので、add_int関数は正常に呼び出すことができています。 静的にリンクしたライブラリーの関数は正常に使えました。 extendable-program-plugin.soはシンボルの解決ができないのでロードに失敗します。

つまり、以下のような状態になります。

画像の説明

-Eオプションをつけてビルドしたプログラムでは、ep-plugin-aggregateが実行できています。 -Eは「全てのシンボルを動的シンボルテーブルに追加する」ので、静的にリンクされたライブラリーが持っているシンボルも動的シンボルテーブルに追加されるため、libadd.aのシンボルをextendable-program-plugin.soから参照できるのです。

つまり、以下のような状態になります。

画像の説明

このように、-Eをつけてビルドすることで、静的なライブラリーで実装されている関数を動的にロードされたライブラリーから参照できるということもわかりました。 特定の静的ライブラリーがリンクされることが前提のプログラムなら、-Eを用いて静的ライブラリーが持つ機能を動的ライブラリーで使うことができます。