ログイン新規登録

Qiitaにログインして、便利な機能を使ってみませんか?

あなたにマッチした記事をお届けします

便利な情報をあとから読み返せます

3
5

この記事は最終更新日から3年以上が経過しています。

RustでCとリンク可能な静的ライブラリを作る

最終更新日 投稿日 2020年04月28日

Rustで静的なライブラリを作ろうと思った時に、ちょっとややこしかったのでメモ。
タイトルにある「Cとリンク可能な」というのは、「Rust専用の形式ではなく使用している環境での一般的な形式の」という意味で、この記事ではLinuxシステムを前提としているので.aファイルのライブラリということになる。

なお、この記事は少し中途半端なところで終わっているので、もし参考にする場合はご注意下さい。

ライブラリの作成

まずはcargoでライブラリ用のプロジェクトを作成する。

$ cargo new --lib mylib

デフォルトではRust用のライブラリ(.rlib)としてビルドされてしまうので、システムの静的ライブラリ(.a)としてビルドするようにcrate-typeをstaticlibに設定する。

Cargo.toml
...
[lib]
crate-type = ["staticlib"]

cargo buildによりビルドすると.aファイルが生成される。

$ cargo build
(出力は省略)
$ ls target/debug/*.a
target/debug/libmylib.a

Cとのリンク

作成したライブラリとリンクするようにCのプログラムをコンパイルする。
しかし、書いた覚えのない関数が見つからないとのエラーが大量に出てしまう。

$ gcc main.c -o main -L /project_path/mylib/target/debug/ -lmylib
usr/bin/ld: /project_path/mylib/target/debug//libmylib.a(std-c32b051c3aafd36c.std.4p3qj3su-cgu.0.rcgu.o): in function `std::sys::unix::mutex::Mutex::init':
/rustc/b8cedc00407a4c56a3bda1ed605c6fc166655447//src/libstd/sys/unix/mutex.rs:46: undefined reference to `pthread_mutexattr_init'
/usr/bin/ld: /rustc/b8cedc00407a4c56a3bda1ed605c6fc166655447//src/libstd/sys/unix/mutex.rs:48: undefined reference to `pthread_mutexattr_settype'
/usr/bin/ld: /rustc/b8cedc00407a4c56a3bda1ed605c6fc166655447//src/libstd/sys/unix/mutex.rs:52: undefined reference to `pthread_mutexattr_destroy'
/usr/bin/ld: /rustc/b8cedc00407a4c56a3bda1ed605c6fc166655447//src/libstd/sys/unix/mutex.rs:46: undefined reference to `pthread_mutexattr_init'
/usr/bin/ld: /rustc/b8cedc00407a4c56a3bda1ed605c6fc166655447//src/libstd/sys/unix/mutex.rs:48: undefined reference to `pthread_mutexattr_settype'
/usr/bin/ld: /rustc/b8cedc00407a4c56a3bda1ed605c6fc166655447//src/libstd/sys/unix/mutex.rs:52: undefined reference to `pthread_mutexattr_destroy'
/usr/bin/ld: /rustc/b8cedc00407a4c56a3bda1ed605c6fc166655447//src/libstd/sys/unix/mutex.rs:46: undefined reference to `pthread_mutexattr_init'
/usr/bin/ld: /rustc/b8cedc00407a4c56a3bda1ed605c6fc166655447//src/libstd/sys/unix/mutex.rs:48: undefined reference to `pthread_mutexattr_settype'
/usr/bin/ld: /rustc/b8cedc00407a4c56a3bda1ed605c6fc166655447//src/libstd/sys/unix/mutex.rs:52: undefined reference to `pthread_mutexattr_destroy'
...
collect2: error: ld returned 1 exit status

関数名から察するに、ここで見つからない関数はlibpthreadやlibdlに含まれるもののようだ。試しにこれらをリンクするように指定してみると、問題なくコンパイルは成功する。(大抵の場合、これらのライブラリはシステムにインストールされておりパスも通っているので、ライブラリを指定するだけでリンクできる。)

$ gcc main.c -o main -L /project_path/mylib/target/debug/ -lmylib -lpthread -ldl

なぜこうなるかというと、RustはデフォルトでlibpthreadなどのCで書かれたライブラリに依存しているためだ。
試しに「Hello, world!」を出力するだけのプログラムにどのようなライブラリがリンクされているか見てみると、libpthreadもlibdlもリンクされていることが分かる。
今回作成したライブラリでlibrtは必要にならなかったが、ライブラリで実装している処理によってはlibrtもリンクしないとコンパイルできなかったりもするだろう。libgcc_sについてはどのようなケースで必要になるのかちょっとよく分からなかった、、、

$ ./hello_rust
Hello, world!
$ ldd hello_rust
        linux-vdso.so.1 (0x00007ffe4e091000)
        libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f044fcb8000)
        librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f044fcae000)
        libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f044fc8d000)
        libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f044fc73000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f044fab2000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f044fd0b000)

ちなみに同じようなプログラムをCで実装してコンパイルした場合は、もう少しリンクされるライブラリは少ない。いくつかライブラリがリンクされているが、これらはgccでコンパイルすれば勝手にリンクしてくれる。(Rustでも同じライブラリがリンクされていることを確認できる。)

$ ./hello_c 
Hello, world!
$ ldd hello_c 
        linux-vdso.so.1 (0x00007ffced5d1000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fd426a3f000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fd426c20000)

ちょっと分かりづらくない?

自分で静的ライブラリを作ってリンクしたつもりなのに、自分で書いてない関数でエラーが出たりして、しかもRustがデフォルトでリンクしているライブラリとか普段意識してないし、、、ライブラリを使うときに自分でそれらを指定しなきゃいけないのも結構面倒。
さっくり自分で作成したライブラリだけ指定すればいいようにできないのだろうか?

結果から言うと、なんとなくやりたいことはできたが、ちょっと強引なやり方になってしまった。

Rustのmuslターゲットを使う

上の例でみたように、Rustではデフォルトでいくつかのライブラリが動的にリンクされるが、ターゲットを変えることで静的にリンクさせることができる。
rustupで選択可能なターゲットを確認してみる。

$ rustup target list
...
x86_64-unknown-linux-gnu (installed)
...
x86_64-unknown-linux-musl
...

リストを確認すると、ほとんどのターゲットにおいて-gnuと-muslが対になっていることが分かる。-gnuをターゲットにした場合はglibcが、-muslをターゲットにした場合はmusl libcがリンクされる。そして、glibcは動的に、musl libcは静的にリンクされる
musl libcが静的リンクを前提としているらしく、それを知ったときはなるほどという感じだったが、ターゲットの名前からリンクのやり方が違うということを読み取れなかったので最初は少し戸惑った。

-muslのターゲットもrustupで簡単にインストール可能だ。

$ rustup target add x86_64-unknown-linux-musl

このmuslターゲットのインストール、日本語による検索でヒットするページだと古い公式ドキュメントなど、musl libcを使うようにrustcを自分でビルドする手順が結構出てくる(2020/4/28時点)。しかし、調べてみると今はrustupで簡単にインストールできるようになっており、勢いのある言語は整備がどんどん進んでありがたいな~と思うのであった。

ターゲットがインストールできたら、cargoにオプションを渡すだけで簡単にターゲットを切り替えられる。
ターゲットを指定した場合、成果物が置かれるディレクトリが変わるので注意。

$ cargo build --target=x86_64-unknown-linux-musl
(出力は省略)
$ ls target/x86_64-unknown-linux-musl/debug/*.a
target/x86_64-unknown-linux-musl/debug/libmylib.a

これで、Rustそのものに必要なライブラリも静的にリンクされている自前のライブラリを作ることができた。

再度、Cとのリンク

ここまでくれば、あとは最初と同じようにコンパイルするだけだ、と思った。

$ gcc main.c -o main -L /project_path/mylib/target/x86_64-unknown-linux-musl/debug/ -lmylib

実際、自分が作成したライブラリだけ指定すれば問題なくコンパイルできたのだが、実行するとSegmentation faultで落ちてしまった。
正確な原因はつかめていないのだが、自作のライブラリにはmusl libcが含まれているにも関わらずC側のプログラムがglibcを動的にリンクしているため、musl libcの処理が呼ばれるべきところでglibcの処理が呼ばれてしまい、何かおかしくなっているのかもしれない。

ちなみに-staticを付けてコンパイルするとうまくいって、当初やりたかったことができる。

$ gcc main.c -o main -L /project_path/mylib/target/x86_64-unknown-linux-musl/debug/ -lmylib -static

Rustのライブラリ側に含まれるmusl libcを優先的に使ってくれるようになるのだろうか、、、うーん、よく分からん。

まとめ

たぶん、Rustのライブラリ側にmusl libcを使うなら、Cのプログラム側もmusl libcを使うようにgccを設定すべきで、glibcとmusl libcをごちゃまぜにして使うのはよくないと思う。
そのあたりも調べてみたいが体力がもたなかったので、今度気が向いたら調べてみよう、、、勉強不足だな~

3
5
0

新規登録して、もっと便利にQiitaを使ってみよう

  1. あなたにマッチした記事をお届けします
  2. 便利な情報をあとで効率的に読み返せます
  3. ダークテーマを利用できます
ログインすると使える機能について

コメント

この記事にコメントはありません。

いいね以上の気持ちはコメントで

Qiita Conference 2024 4月17日(水)~19(金)開催!

Qiita Conferenceは、Qiita最大規模のテックカンファレンスです!

基調講演ゲスト(敬称略)

牛尾剛、 市谷聡啓、 けんすう、 ゆる言語学ラジオ、 田中邦裕、小城久美子、 飯沼亜紀

3
5

ログインして続ける

ソーシャルアカウントでログイン・新規登録

メールアドレスでログイン・新規登録