2013年8月21日水曜日

.soを作って、他のbinaryから呼び出すための初心者向け要点

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

/usr/libとかをlsすれば.soであふれているわけで, 普段カジュアルに使っている人も大変お世話になっているわけです. そんな.soを作ってみようという話です.

.soはshared objectの略らしいです. .soが作られる時点では, 使う側のbinaryが確定しない状態なので, binary内でメモリアドレスが固定できません. そのため, .soは基準となるアドレスを後から与える位置独立なコード(PIC) になっています.

さて具体的にgccのコマンドを見ていきますが, ".oを作る"ことと".so"を作る段階を別に書いた方がよいです. というのもgccのoptionが複雑だからです. それはおもにld(linker)にgccからオプションを渡すためです.

まずfoo.hとfoo.cを用意します. libfoo.soは足し算の機能を提供します.

foo.h

int add(int x, int y);

foo.c

#include "foo.h"

int
add(int x, int y)
{
    return x+y;
}

次に.oを生成します.

% gcc -std=c99 -Wall -fPIC -c foo.c

などと -fPICオプションを指定します.

次に.oから.soを生成させます.

% gcc -shared -Wl,-soname,libfoo.so -o libfoo.so foo.o

オプションの意味は大体こんなところ.詳しく知りたい人はgccのマニュアルをあたってください.

-shared
.soであるので指定します.
-Wl,-soname,libfoo.so
Linkerにオプションが渡るらしいです. -sonameはファイル名ではなく埋め込まれる情報です. これはloaderが呼び出し側が必要とする.so探すときに使うとのこと.
-o
gccに対してoutputするfile名を指定します.
foo.o
入力の指定. foo.oを使え, です

これでlibfoo.soが生成されたはずです. どんなものかreadelfで確認しましょう. systemによって異なる部分があります.

% readelf libfoo.so
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
--略--
  Type:                              DYN (Shared object file)
--略--
Symbol table '.dynsym' contains 11 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
--略--
     7: 000000000000053c    21 FUNC    GLOBAL DEFAULT   11 add
--略--

それっぽいものが含まれてますね.

では, libfoo.cを使ってみましょう.

runner.c とでもしましょうか. foo.cが公開している関数を呼ぶのでfoo.hをincludeする必要があります.

#include <stdlib.h>
#include <stdio.h>

#include "foo.h"

int
main(int argc, char** argv)
{
    printf("%d\n", add(1, 2));
    return 0;
}

こいつをcompileします.

% gcc -std=c99 -Wall -c runner.c

本記事の重要な点1つめ

 gcc runner.o -L. -lmvm -o runner
runner.o
inputの指定 -lmvmよりです.
-L.
探すライブラリのpathに"."を追加します.
-lmvm.
libmvm.soの中を探します. "lib"や".so"は不要です.
-o runner
outputのファイル名

どうしてこういう順序になるかというとStack Overflowの記事からの引用になりますが,

foo.o -lz bar.o

と指定するとzを探すのはfoo.o見た後だが, bar.oを見る前なので, bar.oがzの中味を参照していると, その参照を解決できません!!

ここまでくるとrunnerができているはずです. しかしそのままで実行すると....

% ./runner
./runner: error while loading shared libraries: libfoo.so: cannot open shared object file: No such file or directory

あら残念.

本記事の重要な点2つめデス.

libfoo.soのありかがわからないのでこういうことがおきます. ではどのように教えるか, というといくつかあって:

  1. ldのオプションを用いてrunnerのbinaryに埋め込む.
  2. 環境変数 LD_LIBRARY_PATHを設定する
  3. /etc/ld.so.conf を設定する

今回はシステムにインストールする話ではないので(3)はないです.

(1), (2)は何を作りたいか, libがどこにどのように配置されるかに依存するでしょう

(1)の場合は-Wlを用いてldに-Rを指定します. ロード時に"."が渡されるので, runnerと同じdirにlibfoo.soがあることが期待されます.

% gcc runner.o -L. -lfoo-Wl,-R. -o runner

もしくは

% gcc runner.o -L. -lfoo-Wl,-rpath,. -o runner

ld --helpによると

--略--
 -R FILE, --just-symbols FILE
                              Just link symbols (if directory, same as --rpath)
--略--
 -rpath PATH                 Set runtime shared library search path
--略--

ということらしいです.

いずれの場合もlddで見てみると次のような表示が得られ,埋め込まれていることがわかります

% ldd runner
linux-vdso.so.1 =>  (0x00007fff4b7ff000)
libmvm.so => ./libmvm.so (0x00007f4c03a95000)
libc.so.6 => /lib64/libc.so.6 (0x00000031fac00000)
/lib64/ld-linux-x86-64.so.2 (0x00000031fa400000)

rpathは${ORIGIN}を展開できたりするらしいです. またrpathはdeprecatedだっていう文章も見かけました. ldオプション関係をまとめるのはまたの機会にしたいと思います.

最後に一番気に入らない解決策をば

% export LD_LIBRARY_PATH=.

をするというもの.

% ldd runner
 linux-vdso.so.1 =>  (0x00007fff57cf9000)
 libmvm.so => not found
 libc.so.6 => /lib64/libc.so.6 (0x00000031fac00000)
 /lib64/ld-linux-x86-64.so.2 (0x00000031fa400000)
% export LD_LIBRARY_PATH=.
% ldd runner
 linux-vdso.so.1 =>  (0x00007fffae33b000)
 libmvm.so => ./libmvm.so (0x00007fa5cb1ca000)
 libc.so.6 => /lib64/libc.so.6 (0x00000031fac00000)
 /lib64/ld-linux-x86-64.so.2 (0x00000031fa400000)
% ./runner
3

となり, たしかに動く.

LD_RUN_PATHなるものもあるらしいが、同じくまたの機会, ということで

"."を指定することはあまり感心しない. int addのシグネチャとlibfoo.soの名前が一致するが 悪意のあるコードの実行するlibが置かれたdirでrunnerを起動した場合, そのコードを実行してしまうだろうから.

それではhappy hacking!

0 件のコメント: