/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のありかがわからないのでこういうことがおきます. ではどのように教えるか, というといくつかあって:
- ldのオプションを用いてrunnerのbinaryに埋め込む.
- 環境変数 LD_LIBRARY_PATHを設定する
- /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 件のコメント:
コメントを投稿