なぜか AIX で ruby のコンパイルを始めてみた。手強い。
[環境]
[参考資料]
まず何も考えずに configure; make してみると、 miniruby のリンクで止まる。-brtl がよくないらしい。 これはリンカ (/usr/ccs/bin/ld) のオプションなので、 gcc を使う場合は -Wl,-brtl にする必要がある。
それはともあれ、とりあえず先に進めるために -brtl を取ってみると miniruby は作れて動くようになる。
~/obj/ruby $ make miniruby
gcc main.o libruby-static.a -ldl -lcrypt -lm -o miniruby -g -O2
~/obj/ruby $ ./miniruby -v -e 'puts "OK"'
ruby 1.9.0 (2005-02-14) [rs6000-aix5.1.0.0]
OK
……が、拡張ライブラリがコンパイルできない。
compiling bigdecimal
gcc -g -O2 -I. -I../.. -I../../../../src/ruby -I../../../../src/ruby/ext/bigdecimal -c ../../../../src/ruby/ext/bigdecimal/bigdecimal.c
In file included from ../../../../src/ruby/ruby.h:21,
from ../../../../src/ruby/ext/bigdecimal/bigdecimal.c:23:
../../config.h:7:1: warning: "_ALL_SOURCE" redefined
In file included from /opt/freeware/lib/gcc-lib/powerpc-ibm-aix5.1.0.0/3.3.2/include/ctype.h:32,
from ../../../../src/ruby/ext/bigdecimal/bigdecimal.c:16:
/usr/include/standards.h:99:1: warning: this is the location of the previous definition
/usr/ccs/bin/ld -brtl -eInit_bigdecimal -bI:../../ruby.imp -bM:SRE -T512 -H512 -L"../.." -o ../../.ext/rs6000-aix5.1.0.0/bigdecimal.so bigdecimal.o -ldl -lcrypt -lm -lc
ld: 0706-003 インポート・ファイルが見つからないか、読み取れません: ../../ruby.imp
ld:accessx(): このパス名のファイルまたはディレクトリは存在しません。
make: 1254-004 最後のコマンドからのエラー・コードは 255 です。
停止します。
make: 1254-004 最後のコマンドからのエラー・コードは 1 です。
停止します。
いろいろ調べた結果、make ruby.imp でエクスポートライブラリを作っておく必要があるとわかった。 めんどくさい。
※ さらに後の調べによって、--enable-shared のときは自動的に作成されることが判明
ruby.imp を作っておくとビルドは問題なく最後まで行くようになったが、 今度は拡張ライブラリがロードできない。
~/obj/ruby $ sudo make install 略 ~/obj/ruby $ /usr/local/pkg/ruby/bin/ruby --version ruby 1.9.0 (2005-02-14) [rs6000-aix5.1.0.0] ~/obj/ruby $ /usr/local/pkg/ruby/bin/ruby -rbigdecimal -e0 ./bigdecimal.so: load failed - ./bigdecimal.so (LoadError)
さてどーするかな。
まず、小さいアプリケーションでもって、 dlopen を使ったライブラリルーチンの正しい呼びかたを確認してみる。 構成はできるだけ ruby に似せて次のようにする。
このとき次のようにすると動作するライブラリが作れる。
~/c/test/c/shlib $ make
gcc -c -Wall main.c -o main.o
gcc -c -Wall libruby.c -o libruby.o
gcc -shared -Wl,-G libruby.o -o libruby.so
gcc -c -Wall extlib.c -o extlib.o
gcc -shared -Wl,-G -Wl,-rtllib extlib.o -L. -lruby -o extlib.so
ld: 0711-224 警告: シンボル ._GLOBAL__DI が重複しています。
ld: 0711-224 警告: シンボル ._GLOBAL__DD が重複しています。
ld: 0711-224 警告: シンボル _GLOBAL__DI が重複しています。
ld: 0711-224 警告: シンボル _GLOBAL__DD が重複しています。
ld: 0711-345 -bloadmap または -bnoquiet オプションを使用して、詳細な情報を得てください。
gcc main.o -Wl,-brtl -L. -lruby -o ruby
ターゲット "all" は最新のものです。
~/c/test/c/shlib $ ./ruby
result=25
_GLOBAL__DI, _GLOBAL__DD は C++ のコンストラクタとデストラクタらしい。 無視しておこう。
ポイントは 4 点ある。
拡張ライブラリに libruby をリンクするのは、 rb_xxx_xxxx() をリンクするため。 どうも AIX では単純に dlopen するだけだと 拡張ライブラリ → ruby のシンボル解決ができないっぽい。 だから libruby を独立させて、 コンパイル時にシンボルを解決してしまう必要がある。
また現在の ruby は拡張ライブラリを作るときに ld -eInit_xxxx -bM:SRE -T512 -H512 というフラグを使っているが、 これは必要ないようだ。-G -brtllib だけ付けておけばよい。 man ld には次のようにある。
-G Produces a shared object enabled for use with the run-time linker. The -G flag is equivalent to specifying the erok, rtl, nortllib, nosymbolic, noautoexp, and M:SRE options with the -b flag. Subsequent options can override these options. This flag only applies to AIX 4.2 or later.
共有ライブラリを作るときに必要なのは -bM:SRE オプションなんだが、 これは -G では暗黙のうちに指定される。 また -berok により未定義シンボルが許可されるので、 明示的にシンボルをインポートする必要はない (つまり ruby.imp とかのあたりがすべて必要なくなる)。
念のため他のフラグも見ておく。
-brtl はオブジェクトファイルにライブラリの名前を埋め込むオプションで、 共有ライブラリにリンクするオブジェクトファイルをビルドするときに必要。 また -l オプションのライブラリを探すとき、*.a 以外に *.so も見るようになる。 -G を付けると暗黙のうちに -brtl もセットされる。
ちなみに AIX では共有ライブラリも *.a という名前を持つが、 中身は *.so と一緒 (ECOFF オブジェクトファイル) である。 さらに言えば共有ライブラリはバージョニングされない。 このへんは Tru64UNIX と似ているようだ。
-brtllib は librtl をリンクするフラグで、 dlopen でロードされるライブラリに必要。 -G は暗黙のうちに -bnortllib をセットするので、 -brtllib を明示的に追加する必要がある。
以上の知見をもとに Makefile と config.status を 書き換えまくってみたところ、ビルドまではうまくいった。 しかし拡張ライブラリをロードすると失敗する。
あれ?
あれれ?
……dlopen が使われてないじゃねえかー! これが原因か! dln.c を書き換えて #ifdef AIX を抹殺する。
~/obj/ruby-shared $ ruby -v -rbigdecimal -e 'p $"' ruby 1.9.0 (2005-02-14) [rs6000-aix5.1.0.0] ["bigdecimal.so"]
勝利!
ん? そういえば rpath を設定してないのになんで実行できるんだろ。 もしかしてカレントディレクトリに libruby.so があるからか。 移動するとどうなる?
~/obj/ruby-shared $ cd
~ $ ruby --version
exec(): 0509-036 以下のエラーのためにプログラム ruby をロードできません:
0509-150 従属モジュール libruby.so をロードできませんでした。
0509-022 モジュール libruby.so をロードできません。
0509-026 システム・エラー: このパス名のファイルまたはディレクトリは存在しません。
だめじゃん……。
どーすればいいんだろう。 ld -blibpath:$prefix/lib:/usr/lib かな?
~/obj/ruby-shared $ gcc -Wl,-brtl -Wl,-blibpath:/usr/local/pkg/ruby/lib:/usr/lib main.o -L. -lruby -ldl -lcrypt -lm -lc -o ruby ~/obj/ruby-shared $ sudo cp ruby /usr/local/pkg/ruby/bin/ ~/obj/ruby-shared $ cd ~ $ ruby --version ruby 1.9.0 (2005-02-14) [rs6000-aix5.1.0.0]
うまくいったもよう。確認。
~ $ dump -H /usr/local/pkg/ruby/bin/ruby
/usr/local/pkg/ruby/bin/ruby:
***Loader Section***
Loader Header Information
VERSION# #SYMtableENT #RELOCent LENidSTR
0x00000001 0x00000015 0x00000071 0x0000004e
#IMPfilID OFFidSTR LENstrTBL OFFstrTBL
0x00000004 0x00000764 0x0000005d 0x000007b2
***Import File Strings***
INDEX PATH BASE MEMBER
0 /usr/local/pkg/ruby/lib:/usr/lib
1 libruby.so
2 libc.a shr.o
3 librtl.a shr.o
ちゃんとセットされているようだ。
さて、パッチをどうするかな。 よーするに、パラメータをこうすりゃいいはずなんだが。
LDSHARED = gcc -shared LDFLAGS = -Wl,-brtl -Wl,-blibpath:$(prefix)/lib:/usr/lib -L. -lruby DLDFLAGS = -Wl,-G -Wl,-brtllib $(EXTLDFLAGS) XLDFLAGS = $(EXTLDFLAGS) EXTLDFLAGS = LIBRUBYARG = -L$(topdir) -lruby
あと dln.c の AIX 独自コードを捨てて dlopen にする。 これで全部通るはずだ。
問題は古いバージョンの AIX だなあ。 man を見ると、4.2 より前とそれ以降でかなり劇的に変わっているようだ。 4.1 以前は無視、てことならば、 上記のように変更してしまえばすべてカタが付く。 無視しないのなら、これまでのパラメータを残しつつ変更する必要がある。
(07:05)
そーいえば socket がコンパイルされてなかった。 mkmf.log を見る限り、IPv6 がらみの getaddrinfo がだめみたいだ。 てっとりばやく --enable-wide-getaddrinfo で通す。
~/obj/ruby-shared/ext/socket $ ruby ../../../../src/ruby/ext/socket/extconf.rb --enable-wide-getaddrinfo
略
~/obj/ruby-shared/ext/socket $ make
略
~/obj/ruby-shared/ext/socket $ ruby -rsocket -e '
s = TCPSocket.open("harmony", 80)
s.puts "GET / HTTP/1.0"
s.puts
puts s.read
s.close
'
HTTP/1.1 200 OK
Date: Wed, 16 Feb 2005 22:13:40 GMT
Server: Apache/1.3.28 (Unix) mod_fastcgi/2.4.2 mod_ruby/1.0.7 Ruby/1.9.0 mod_ssl/2.8.15 OpenSSL/0.9.7b
Last-Modified: Fri, 05 Mar 2004 15:40:57 GMT
ETag: "22529-ff-40489f89"
Accept-Ranges: bytes
Content-Length: 255
Connection: close
Content-Type: text/html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=us-ascii">
<title>harmony</title>
</head>
<body>
<h1>harmony</h1>
<p>Apache working on harmony</p>
</body>
</html>
解決。
(07:17)
AIX 用のエクスポートファイルを作る方法。
ruby は nm を使っているが、 dump を使ってもよいらしい。
# mkexp.rb
table = {}
`dump -g #{ARGV[0]}`.each do |line|
next unless /\A\s*\d/ =~ line
idx, name = line.split
table[name.sub(/\A\./, '')] = true
end
puts table.keys.sort
(07:22)