ローファイ日記

出てくるコード片、ぼくが書いたものは断りがない場合 MIT License としています http://udzura.mit-license.org/

完全なワンバイナリーmruby

タイトルは 575 です。相変わらず気ぜわしいので小ネタを投げます。

mruby は「ワンバイナリ」を作れるRubyとして知られています。ですが、実際にはlibcなどをダイナミックリンクします。それはそうでしょうという感じですが、例えばLinuxでmruby-bin-mrubyでmrubyバイナリを作ると、こんな感じで確認できます。

$ ldd ./bin/mruby
        linux-vdso.so.1 (0x00007ffca74f3000)
        libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f9bd477e000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f9bd438d000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f9bd4de4000)

なので、これらのダイナミックリンクを、スタティックリンクに変えれば「真の」ワンバイナリと言えるわけです。ぼくも「not a dynamic executable」と言われたい(?)。

変更箇所としてはとりあえず多くなく、2箇所で、mrubyの公式リポジトリをチェックアウトし、以下を変えます。

  • mruby-bin-mruby でccとlinkerのフラグで -static -lc -lm をツッコむ
  • 最小構成何も出力できないため mruby-io だけgemとして使う
diff --git a/build_config.rb b/build_config.rb
index 751317c7..4bf4e23c 100644
--- a/build_config.rb
+++ b/build_config.rb
@@ -18,7 +18,7 @@ MRuby::Build.new do |conf|
   # end
   # conf.gem 'examples/mrbgems/c_and_ruby_extension_example'
   # conf.gem :core => 'mruby-eval'
-  # conf.gem :mgem => 'mruby-io'
+  conf.gem core: 'mruby-io'
   # conf.gem :github => 'iij/mruby-io'
   # conf.gem :git => 'git@github.com:iij/mruby-io.git', :branch => 'master', :options => '-v'
 
diff --git a/mrbgems/mruby-bin-mruby/mrbgem.rake b/mrbgems/mruby-bin-mruby/mrbgem.rake
index 280621e3..2325512f 100644
--- a/mrbgems/mruby-bin-mruby/mrbgem.rake
+++ b/mrbgems/mruby-bin-mruby/mrbgem.rake
@@ -7,6 +7,9 @@ MRuby::Gem::Specification.new('mruby-bin-mruby') do |spec|
   spec.add_dependency('mruby-error', :core => 'mruby-error')
   spec.add_test_dependency('mruby-print', :core => 'mruby-print')
 
+  spec.cc.flags += ["-static", "-lc", "-lm"]
+  spec.linker.flags += ["-static", "-lc", "-lm"]
+
   if build.cxx_exception_enabled?
     build.compile_as_cxx("#{spec.dir}/tools/mruby/mruby.c", "#{spec.build_dir}/tools/mruby/mruby.cxx")
   end

この状態でビルドしなおすと、こういうデカ目のワンバイナリ(といいつつ、静的リンクなしでも3.8MBぐらいっぽいんですが)ができ、何者にもダイナミックリンクしていないことが確認できます。

$ ls -lh ./bin/mruby
-rwxrwxr-x 1 vagrant vagrant 5.8M Jun 19 11:46 ./bin/mruby
$ ldd ./bin/mruby
        not a dynamic executable

普通にmrubyとして動きますが、ここで ./binchroot した状態、つまり完全に mruby バイナリしかないファイルシステムの状態で動かしても、動きます。

$ sudo chroot ./bin /mruby -e '10.times { p "Hello from static binary!!" }'
"Hello from static binary!!"
"Hello from static binary!!"
"Hello from static binary!!"
"Hello from static binary!!"
"Hello from static binary!!"
"Hello from static binary!!"
"Hello from static binary!!"
"Hello from static binary!!"
"Hello from static binary!!"
"Hello from static binary!!"

なお、普通のmrubyだとこうなります。 No such file or directory と言っていますが mruby バイナリはあります。しかし、依存している libc.so などが見つからないとこういうエラーになるのです。

$ sudo chroot ./bin /mruby -e '10.times { p "Hello from static binary!!" }'
chroot: failed to run command ‘/mruby’: No such file or directory

これを応用してワンバイナリだけをコンテナに置いたら機能するような最小限のイメージが作れると思われる。

実際には NSS 周りの問題 や、そもそも依存するmgemがどのライブラリにダイナミックリンクするかを全部把握するなど、ハードコアではあるんですが、まずは雑なメモであった。

そして変なところがあれば教えて欲しいのであった。


参考

fukasawah.github.io