OCamlで作成した拡張モジュールでセグメンテーション違反発生
以前のブログでも書いたとおり,弊社ではOCamlを使ってLL言語用の拡張ライブラリを作成しています。
この構成でこれまでは特に問題なく稼動していたのですが,機能拡張していくうちに突然セグメンテーション違反が発生するようになってしまい困りました。
現在のところ根本的な原因は判明していないのですが,これまでの調査結果と見つかった回避策をいったんまとめておきます。
現象
セグメンテーション違反は,64ビット版Linuxのみで発生します。32ビット版LinuxやMac OS Xでは発生しません。使用するLL言語によっても違いがあり,Pythonでは発生せず,PerlとRubyで発生します(PHPは未確認)。
動作環境は,CentOS 5.5 です。
gdbによる解析
OCamlで作成した共有ライブラリをfoo.soとすると,その共有ライブラリを各LL言語用の拡張ライブラリfoo_py.so,foo_rb.so, foo_pl.soが利用する構造になっています。foo.soは共通なので,呼び出し方によってセグメンテーション違反が発生したりしなかったりすることになります。
違いがどこにあるのか動作をデバッガで追ってみました。
まず,セグメンテーション例外が発生する直接の原因は範囲外のメモリにアクセスしたためでした。rsiレジスタにはデータ格納領域のアドレスが格納されているはずなのですが,でたらめな値になっています。
(gdb) info reg rax 0x930000002a95f07e -7854277749419675522 rbx 0x2a95f05798 182904182680 rcx 0x2a95f05002 182904180738 rdx 0x7bb 1979 rsi 0x930000002a95f07e -7854277749419675522 rdi 0x2a95c13b70 182901095280 rbp 0x2a95f057f8 0x2a95f057f8 rsp 0x7fbfffe810 0x7fbfffe810 r8 0x1 1 r9 0x1 1 r10 0x0 0 r11 0x2a95ebd630 182903887408 r12 0x2a95f06180 182904185216 r13 0x2a95f06078 182904184952 r14 0x7fbfffe9d0 548682066384 r15 0x2a95f05768 182904182632 rip 0x2a95c13b77 0x2a95c13b77 <camlField__Pdfreport__set_field_value_3874+7> eflags 0x10202 66050 cs 0x33 51 ss 0x2b 43 ds 0x0 0 es 0x0 0 fs 0x0 0 gs 0x0 0
後は,レジスタが破壊される場所を絞り込んでいけば原因が推測できそうですが,これが一筋縄では行きませんでした。
この手のローレベルなデバッグではありがちなことですが,ブレークポイントを設定することでプログラムの動作が影響を受けてしまうようなのです。ブレークポイントを細かく設定して発生場所を絞り込んでいくと,捜査範囲外で例外が発生するなどしてなかなかうまくいきません。
ただ,試行錯誤を続けるうちになんとなく傾向が見えてきました。以下のような“XXX@plt”という形式のシンボルへサブルーチンコールした直後にレジスタが破壊されることが多いようなのです。
callq 0x2a95b2e780 <camlField__Extlib__ExtList__loop_1124@plt>
“XXX@plt”というのは,動的リンクを行う際に遅延リンクを実現するための仕組みです。PLTと呼ばれるテーブルにサブルーチンのアドレスが並んでいて,サブルーチンコールはPLTを介して間接的に行うことになります。共有ライブラリロードした直後にはPLTには実際のアドレスは格納されておらず,最初のサブルーチンコールを行った際に動的にシンボルを解決します。
動的リンクの仕組みの詳細については,参考文献を参照してください。特に「リンカ・ローダ実線開発テクニック」が非常に詳しくてお勧めです。
共有ライブラリ呼び出し側の違い
一方,Pythonで動いてRubyやPerlで動かないということは,共有ライブラリの呼び出し方に何か違いがあるのかもしれません。
Rubyのソースを調べたところ,RTLD_LAZYフラグを指定して共有ライブラリをオープンしていました。
/* Load file */ if ((handle = (void*)dlopen(file, RTLD_LAZY|RTLD_GLOBAL)) == NULL) { error = dln_strerror(); goto failed; }
Pythonでは,フラグの値が変数に格納されているので動作時に実際にどのモードで動くのかをソースから解析するのは困難でしたが,少なくともdlopenflagsの初期値としてはRTLD_NOWが設定されているようでした。
#if !(defined(PYOS_OS2) && defined(PYCC_GCC)) dlopenflags = PyThreadState_GET()->interp->dlopenflags; #endif handle = dlopen(pathname, dlopenflags);
RTLD_LAZYモードでは,シンボル解決が最初の呼び出し時まで遅延されます。RTLD_NOWモードでは,共有ライブラリのロード時にシンボル解決が行われます。
実際にデバッガでステップ実行すると,Rubyから起動した場合は“XXX@plt”のコール後にシンボル解決ルーチンへジャンプしますが,Pythonから起動した場合はPLTテーブルに既に有効なアドレスが格納されていてシンボル解決ルーチンへのジャンプは発生しません。
考察
遅延シンボル解決ルーチンは決して実行環境に影響を与えないように設計されているはずですが,何らかの理由により実行環境に影響を与えてしまっているのではないでしょうか?
例えば,以下のような理由が考えられます。
- OCamlが出力するコードが,遅延シンボル解決利用時の規約に違反している。
- 共有ライブラリが,シンボル解決ルーチンの想定を超える構造になっている(例えば,シンボル数が多すぎるなど)。
ただ,遅延シンボル解決を利用する際の規約のようなものが存在するのかどうか分かりませんし,私自身のマシン語の知識が8ビット時代で止まっているので,マシン語コードがなにをやっているのか深く理解するのが困難な状況です。これ以上の深追いができなくて,調査が中断しているというのが実情です。
ちなみに,64ビット版のみで問題が発生する理由は,OCamlが出力するアセンブラコードの性質が異なるためです。32ビット版では遅延シンボル解決に対応した“XXX@plt”形式のシンボルを出力しません。
回避策
共有ライブラリロード時にRTLD_NOWモードで開かせることができれば,回避することができます。
幸い,環境変数LD_BIND_NOWにより遅延シンボル解決を抑制することができます。
$ export LD_BIND_NOW=1
この方法では他の共有ライブラリにも影響を与えてしまいますが,共有ライブラリを作成する際にリンカに“-z now”オプションを与えれば,遅延シンボル解決を抑制する対象を絞り込むことができます。
$ gcc -shared -Wl,-z,now -o foo.so foo.o
“-z now”オプションを付ける方法で,しばらく様子を見てみようと思います。
現在契約できるプロバイダフリーのIP電話
(c) nakusa123|画像素材 PIXTA
現在,050 plusのようなスマートフォンから利用できるIP電話サービスが開始され好評の様ですが,使用する端末やアプリが限定されてしまうのがちょっと不満に思えます。一方,各ISPがひかり電話を始めとするIP電話サービスを提供していますが,この場合は使用する回線が限定されてしまいます。
好みのIP電話アプリや,自宅サーバにインストールしたAsteriskなどのSIPサーバに自由に登録して使用することはできないものでしょうか?
このような制限のないIP電話サービスは「プロバイダフリーのIP電話サービス」と呼ばれ,海外にはたくさん存在します。日本でも2005年前後にいくつか存在したのですが,規制か何かの影響ですっかり廃れてしまいました。
それから数年経って,またボチボチとサービスが立ち上がってきたようですので,現在契約可能なプロバイダフリーのIP電話を調べて見ました。
選定の条件は,以下のとおりです。
- 使用回線が限定されない(プロバイダフリー)。
- 標準的なIP電話プロトコル(SIP等)に準拠していて,専用アプリケーションを必要としない。
- 日本の電話番号が取得できる。
- 個人で契約できる。
どこでもIPフォン
URL:http://www.ip-denwa.com/
電話番号:03-xxxx-yyyy
月額基本料金:1,000円
通話料金:(固定電話)8.4円/3分,(携帯電話)18.27円/分
その他の特長:内線通話,転送電話設定可能
FleaLine Light
URL:http://www.covia.jp/net/flealine01.html
電話番号:050-xxxx-7777 または 03-xxxx-yyyy(オプション)
月額基本料金:400円
通話料金:(固定電話)8.4円/3分,(携帯電話)16.8円/分
その他の特長:転送電話設定可能(オプション)
Phytter
URL:http://www.phytter.com/
電話番号:050-xxxx-7777 または 03-xxxx-eye
月額基本料金:5ドル/月
通話料金:(固定電話)3セント/分,(携帯電話)15セント/分
その他の特長:PayPalでドル決済
VMware ESXi 5.0でVNC接続を利用する方法
最近,VMware ESXi を5.0に移行したところ,今まで使えていたVNCでの接続ができなくなってしまいました。
VMWareサーバにログインしてログファイルを調べてみたところ,以下のようなメッセージを発見。
vmauthd.log:2011-09-05T14:47:33Z vmauthd[11156]: vthread-3| VMAuthdSocketWrite: writing: 220 VMware Authentication Daemon Version 1.10: SSL Required, ServerDaemonProtocol:SOAP, MKSDisplayProtocol:VNC , VMXARGS supported
VMware ESXiでのVNC接続を有効にする方法
以下の記事などを参考にVNC接続を有効にする設定を行ってください。
なお,*.vmxを直接編集しなくても,VMware vSphere Clientの「仮想マシンのプロパティ/オプション/詳細/全般/構成パラメータ…」からパラメータを変更することができます。
SSHのポートフォワーディング機能を使う方法
ターミナルから以下のコマンドを実行します。
$ ssh -L <ローカルホストポート>:localhost:<リモートホストポート> <ユーザ名>@<VMwareサーバ名/アドレス>
ここで,リモートホストポートは,各仮想マシンに割り当てたポート番号です(通常,5900以上)。
ローカルホストポートは任意のポート番号で良いのですが,私はリモートホストポートと同じ番号にしています。
ユーザ名は,VMwareサバーへログインできるユーザ名です(通常“root”?)。
SSH Tunel Managerを使う方法(Mac OS X専用)
SSH Tunel Managerをインストールして,上記のSSHと同様の設定を行います。
設定が終わったらウインドウを閉じて「再生」ボタンを押せば,SSHトンネリングが確立します。
iCalとiPhoneをTODO込みで同期をとる方法
iCalとiPhoneを連携させるには Google カレンダーを媒介として同期を取る方法が定番ですが,残念なことに TODO(リマインダー)の同期が取れません。
GoTasksなど,iPhoneからGoogle の「ToDo リスト」を参照できるようにするAppは存在するようなのですが,iCalとGoogleを連携させる方法がどうしても見つかりませんでした。
そこで発想を変えて Yahoo! Calendar を媒介とする方法を実験してみたところ,うまくいきましたので報告します。
iPhoneにYahoo!アカウントの情報を登録する
つぎは,iPhone に Yahoo! アカウントの情報を登録します。
「設定」 → 「メール/連絡先/カレンダー」 と進んで「アカウントを追加…」を選択し,「Yahoo!」のロゴを選びます。
これで,iPhone と Yahoo! Calendar の同期が取れるようになりました。
「カレンダー」アプリを起動すると,スケジュールが確認できます。
ただし,iOSの標準アプリだけでは TODO を参照することができません。
ここまでうまく行ったのに残念!
BusyToDoをインストールして,Yahoo!アカウントの情報を登録する
そこで,Yahoo! Calendar のクライアントとなるアプリがないか色々探したのですが見つかりません。
最後に,iCalがCalDAVを使っているのを思い出して,CalDAVに対応しているタスク管理アプリを試してみたところ,BusyToDo(850円)で成功しました!!
BusyToDoは,本来MobileMeを想定してCalDAVに対応しているようですが,「ユーザ名」にyahooのメールアドレスを入力したところあっさり接続できました。
他には,2Do(600円)がMobileMe(CalDAV)に対応しているようだったので試してみましたが,こちらはうまく接続できませんでした。
まとめ
Yahoo! Calendar を媒介とすることで,iCalとiPhoneのスケジュールとタスクを同期させることに成功しました。主に使用するiCalでスケジュールとタスクの管理が一元化できるようになったので,とてもすっきりしました。
ただ,iCloud のサービスが開始されれば,このようなテクニックは不要になるんでしょうね。