単体でも実行可能な共有ライブラリの作り方

Fedora 17に含まれる実行可能な共有ライブラリ - Okiraku Programming の続きです。


さて、前記事で説明した glibc のような実行可能なshared libraryを作るにはどうすれば良いか? が気になるところですね?


基本的にはentry pointを指定して共有ライブラリを作成すればOKです。

が、ちょっと罠があって、普通に共有ライブラリを作ってしまうと、プログラムヘッダ中に存在するインタプリタ情報(INTERP)が設定されないため、他のライブラリといっさいリンクされない状態で実行が開始されてしまい、printf などのライブラリ関数が全く呼べない状態になってしまいます。これでもアセンブリで直接システムコール呼び出しすれば、メッセージ表示などもできないことはないのですが…(もちろんglibcはそうしてます)。


test.c

#include <stdio.h>
#include <stdlib.h>
void msg() {
	puts("libtest-0.1");
	exit(0);
}
% gcc -shared -fPIC -e msg test.c -o libtest.so      # -e msg でエントリポイントを指定
% ./libtest.so
zsh: segmentation fault  ./libtest.so

↑落ちた… (gdbでトレースしてみると、puts@pltを解決しようとして、未リンクの場所に飛んでしまっています。)

% readelf -l libtest.so

Elf file type is DYN (Shared object file)
Entry point 0x6b0
There are 6 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000764 0x0000000000000764  R E    200000
  LOAD           0x0000000000000768 0x0000000000200768 0x0000000000200768
                 0x0000000000000238 0x0000000000000240  RW     200000
  DYNAMIC        0x0000000000000788 0x0000000000200788 0x0000000000200788
                 0x00000000000001c0 0x00000000000001c0  RW     8
  NOTE           0x0000000000000190 0x0000000000000190 0x0000000000000190
                 0x0000000000000024 0x0000000000000024  R      4
  GNU_EH_FRAME   0x00000000000006e4 0x00000000000006e4 0x00000000000006e4
                 0x000000000000001c 0x000000000000001c  R      4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     8
...

インタプリタ情報(INTERP)がない。


それならと、ld に --dynamic-linker=/lib64/ld-linux*.so でも渡せば良いのかと思いきや、-shared指定があるとこれでもうまくいきません。


仕方ないので、ライブラリのソース中に .interp セクションを指定してINTERPを作らせるようにします。(→参考: http://gcc.gnu.org/ml/gcc-help/2003-07/msg00232.html )

test.c

#include <stdio.h>
#include <stdlib.h>
const char interp[] __attribute__((section(".interp"))) = "/lib64/ld-linux-x86-64.so.2";
void msg()
{
	puts("libtest-0.1");
	exit(0);            /* この関数を抜ける前に終了しないとSEGVで異常終了する */
}
% gcc -shared -fPIC -e msg test.c -o libtest.so      # -e msg でエントリポイントを指定
% ./libtest.so
libtest-0.1

↑実行できた! (もちろんshared libraryとして、他のプログラムから gcc -ltest 〜でリンクして関数呼び出しもできます。)

% readelf -l libtest.so

Elf file type is DYN (Shared object file)
Entry point 0x740
There are 8 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000000040 0x0000000000000040
                 0x00000000000001c0 0x00000000000001c0  R E    8
  INTERP         0x0000000000000780 0x0000000000000780 0x0000000000000780
                 0x000000000000001c 0x000000000000001c  R      10
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x000000000000081c 0x000000000000081c  R E    200000
  LOAD           0x0000000000000820 0x0000000000200820 0x0000000000200820
                 0x0000000000000238 0x0000000000000240  RW     200000
  DYNAMIC        0x0000000000000840 0x0000000000200840 0x0000000000200840
                 0x00000000000001c0 0x00000000000001c0  RW     8
  NOTE           0x0000000000000200 0x0000000000000200 0x0000000000000200
                 0x0000000000000024 0x0000000000000024  R      4
  GNU_EH_FRAME   0x000000000000079c 0x000000000000079c 0x000000000000079c
                 0x000000000000001c 0x000000000000001c  R      4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     8
...

今度はINTERPが指定されており、ちゃんとライブラリ呼び出しも含めて実行できいることが分かります。