miauのブログ

はてなダイアリー「miauの避難所」をはてなブログに移行しました

64-bit CentOS 上の 32-bit Ruby に gem で拡張ライブラリをインストール

タイトルがごちゃごちゃしてますが、32-bit の Ruby がインストールされている 64-bit OS 上で gem install ruby-debug してもうまくインストールできなかったので、原因&解決策についてです。

前置き

Linux 環境では自分で Ruby をビルドする or 環境に合わせたパッケージを入れることが多いので「なんで 64-bit 環境で 32-bit の Ruby を入れてるの?」という疑問がありそうですが、今の案件の Redmine が 64-bit CentOSRHEL かも?)上に BitNami Redmine Stack を使って構築されているからです。

検証用に私も同じ構成で Redmine を立ててみたんですが、確かにバイナリをダウンロード→実行して、Redmine の管理者情報、SMTP サーバの情報くらいを入力すればすぐに使えるので導入はすごく楽でした。

発生した問題

この環境でちょっと動作が怪しいプラグインがあって ruby-debug でステップ実行したかったのですが、

yum install gcc
gem install ruby-debug
ruby script/server -e production -u

とやっても、なぜか

You need to install ruby-debug to run the server in debugging mode. With gems, use 'gem install ruby-debug'

と言われてしまいます。gem list -d ruby-debug とか gem which ruby-debug で確認しても問題なくインストールはされているようなのですが・・・。

原因&解決策

長くなるので先に原因&解決策を書いておくと、BitNami Redmine Stack でインストールされる Ruby が 32-bit なのに、gem install 時に gcc が 64-bit のバイナリを作っており、うまくライブラリをロードできていないのが原因です。

もし ruby-debug をもうインストールしてしまったのであれば、一度

gem uninstall linecache ruby-debug ruby-debug-base

でアンインストール後、gcc で 32-bit のバイナリを生成できるよう

yum install glibc-devel.i386

を実行した後で、

env CONFIGURE_ARGS="--with-cflags='-m32' --with-ldflags='-m32'" gem install ruby-debug --no-ri --no-rdoc

のように CONFIGURE_ARGS を指定してやるとよいです。

原因特定までのいろいろ

エラーの詳細を出力してみる。

どうもソースを見ると require 以外の失敗でもこのメッセージを出しているみたいなので、vendor/rails/railties/lib/rails/rack/debugger.rb を書き換えて詳細なメッセージを出力してみます。

--- -   2011-05-12 07:38:00.502458000 +0900
+++ ./vendor/rails/railties/lib/rails/rack/debugger.rb  2011-05-12 07:35:42.000000000 +0900
@@ -10,8 +10,9 @@
         ::Debugger.start
         ::Debugger.settings[:autoeval] = true if ::Debugger.respond_to?(:settings)
         puts "=> Debugger enabled"
-      rescue Exception
+      rescue Exception => e
         puts "You need to install ruby-debug to run the server in debugging mode. With gems, use 'gem install ruby-debug'"
+        puts e.message
         exit
       end

まあこんなことしなくても ruby -rrubygems -e'require "ruby-debug"' とかでも同じメッセージは拾えますけど・・・エラーメッセージはこんなでした。

/opt/redmine-1.1.3-0/ruby/lib/ruby/gems/1.8/gems/ruby-debug-base-0.10.4/lib/ruby_debug.so: wrong ELF class: ELFCLASS64 - /opt/redmine-1.1.3-0/ruby/lib/ruby/gems/1.8/gems/ruby-debug-base-0.10.4/lib/ruby_debug.so

調べてみると、32-bit のプログラムが 64-bit のライブラリを呼ぼうとすると出るエラーなんだとか。

バイナリの情報を見てみると・・・

# file /opt/redmine-1.1.3-0/ruby/bin/.ruby.bin
/opt/redmine-1.1.3-0/ruby/bin/.ruby.bin: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.2.5, dynamically linked (uses shared libs), for GNU/Linux 2.2.5, not stripped

# file /opt/redmine-1.1.3-0/ruby/lib/ruby/gems/1.8/gems/ruby-debug-base-0.10.4/lib/ruby_debug.so
/opt/redmine-1.1.3-0/ruby/lib/ruby/gems/1.8/gems/ruby-debug-base-0.10.4/lib/ruby_debug.so: ELF 64-bit LSB shared object, AMD x86-64, version 1 (SYSV), not stripped

確かにそのようで。

gem install のオプションでなんとかならない?→効果なし

gem install に --platform オプションがあるのでこれを使えばいいのかなと思ったんですが、うまくいかず。

よくよく考えるとこれはどの gem を使うかのオプションでしょうし、gem environment で確認したらもともと x86-linux になっていましたから、設定しても意味はなさそうですね。

gem install の詳細を追って見る

gem install に -V オプションを付加すると詳細なログが出力されるそうで。ビルドの箇所を抜き出すと、

/opt/redmine-1.1.3-0/ruby/bin/ruby extconf.rb

gcc -I. -I/opt/redmine-1.1.3-0/ruby/lib/ruby/1.8/i686-linux -I/opt/redmine-1.1.3-0/ruby/lib/ruby/1.8/i686-linux -I. -DAI_ADDRCONFIG=0 -I/opt/redmine-1.1.3-0/common/include  -D_FILE_OFFSET_BITS=64  -fPIC -I/opt/redmine-1.1.3-0/common/include   -c ruby_debug.c
gcc -I. -I/opt/redmine-1.1.3-0/ruby/lib/ruby/1.8/i686-linux -I/opt/redmine-1.1.3-0/ruby/lib/ruby/1.8/i686-linux -I. -DAI_ADDRCONFIG=0 -I/opt/redmine-1.1.3-0/common/include  -D_FILE_OFFSET_BITS=64  -fPIC -I/opt/redmine-1.1.3-0/common/include   -c breakpoint.c
gcc -shared -o ruby_debug.so ruby_debug.o breakpoint.o -L. -L/opt/redmine-1.1.3-0/ruby/lib -Wl,-R/opt/redmine-1.1.3-0/ruby/lib -L. -L/opt/redmine-1.1.3-0/common/lib -rdynamic -Wl,-export-dynamic    -lpthread -lrt -ldl -lcrypt -lm   -lc

こんな感じになっていました。gcc に 32-bit でコンパイルするためのオプションを渡してやればよさそうですね。

gcc で 32-bit バイナリを生成するには?
  • m32 オプションを付加してコンパイルすればいいそうです。

適当なファイルで試してみましたが、適切なヘッダファイルがないと

# gcc -m32 hello.c
In file included from /usr/include/features.h:352,
                 from /usr/include/stdio.h:28,
                 from hello.c:1:
/usr/include/gnu/stubs.h:7:27: error: gnu/stubs-32.h: No such file or directory

のようなエラーになるので、

yum install glibc-devel.i386

の実行が必要でした。

extconf.rb 経由で gcc オプションを渡すには?

を見てもちゃんと書いてませんが、extconf.rb 実行時 --with-cflags='〜' のようにオプションを渡すと Makefile 中で CFLAGS が設定されるようです。

によると

export CONFIGURE_ARGS="--with-cflags='〜'"

のように CONFIGURE_ARGS を設定しておけば、実行時にオプションを渡さなくても extconf.rb(mkmf)で使われるんだとか。

今回は CFLAGS に -m32 を設定したいので、

CONFIGURE_ARGS="--with-cflags='-m32'" gem install ruby-debug --no-ri --no-rdoc -V

を試してみると・・・コンパイルは通りましたが、リンクのタイミングで

/usr/bin/ld: warning: i386 architecture of input file `ruby_debug.o' is incompatible with i386:x86-64 output

のエラーになってしまいました。

リンクに使われるオプションは ldflags、dldflags、archflag の 3 種類があってどれが適切かわからないんですが、とりあえず

env CONFIGURE_ARGS="--with-cflags='-m32' --with-ldflags='-m32'" gem install linecache --no-ri --no-rdoc -V

のようにすると、無事インストールが完了しました。ふぅ。

動作確認

動作させる前にバイナリをいちおう確認しておくと・・・

# file /opt/redmine-1.1.3-0/ruby/lib/ruby/gems/1.8/gems/ruby-debug-base-0.10.4/lib/ruby_debug.so
/opt/redmine-1.1.3-0/ruby/lib/ruby/gems/1.8/gems/ruby-debug-base-0.10.4/lib/ruby_debug.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), not stripped

ちゃんと 32-bit になってますね。

ruby-debugger を実際に動作させてみると・・・

ruby script/server -e production -u

でも

ruby script/console production --debugger

でも、ちゃんと

=> Debugger enabled

と表示され、breakpoint 実行時にちゃんと rdb が有効になりました。めでたしめでたし。