VPNサーバ(PPTPサーバ)付きのルータ設定・調査メモ

in PPTP, VPN
by ginriki | 1 月 17th, 2011 

少し前に、ルータ(WZR-HP-AG300H)を買って自宅外からVPN接続してます。設定がWebブラウザから簡単にできますし、VPNサーバ用のマシンをルータと別に用意する必要もないので、結構便利です。

私の主な使い方は、

  • Android端末からVPN接続し、DAAP Media Playerで自宅のメディアサーバー1の曲を聴く。
  • PCからVPN接続+Wake on Lan(WoL)2で、自宅サーバを立ち上げてログイン。

の2つです。

せっかくなので、設定メモを残しておきます。

VPNサーバ/クライアントの設定


このルータのVPNサーバの方式はPPTPです。設定手順はルータのマニュアルが詳しいです。

ちなみに、私はDDNSにDynDNSを使ってます。

マニュアルに見当たらなかった注意点として、クライアント側のPCに付与されるIPがプライベートIPで、クライアント側ルータでNATが行われる場合は、クライアント側ルータのPPTP(VPN)パススルー/マルチパススルーを有効にしないと、PPTPサーバ・クライアント間で接続できない点があります。

最近のルータなら、たいていルータの設定項目で有効にできます。ルータによっては、GREパケットを通すようにNAT設定する必要があったりします。3

ただ、パススルー設定ができない環境(公衆無線LANとか)から自宅マシンにつなぎたいときもあります。
私の場合、Android端末から3G回線でVPN接続→WoL実施→マシン起動を行った上で、手元のPCから起動したマシンにsshログイン(とポートフォワーディング)で対応してます。

ポート(サービス)チェック処理をtcpdumpして見る



無事VPN接続したあと、http://<ルータのプライベートIP>/hosts.html にアクセスすると、WoLやhttpサーバなどのサービス一覧が表示されます。

httpサーバが動いているかどうかのポートチェックが、どのタイミングで行われるのか気になったので、tcpdumpでLAN内を監視したところ、以下に対してチェックしているようです。。

  • ブロードキャストやルータ宛てのARP Requestを送った送り元IP
  • ブロードキャストやルータ宛てのARP Replyを送った送り元IP

用は、ルータに届いたARPの送り元IPに対して、ARPが届いた直後にポートチェックしているようです

以下は、あるマシン(desktop-ubuntu.local)がプロードキャストARP(Target IP:192.168.1.7)を送った時のtcpdump出力(をちょっと省略したもの)です。ルータ(192.168.1.1)がftpやwwwなどのポートをチェックしているのがわかります。

ethertype ARP (0x0806), length 42: Request who-has 192.168.1.7 (Broadcast) tell desktop-ubuntu.local
ethertype IPv4 (0x0800), length 74: 192.168.1.1.1584 > desktop-ubuntu.local.ftp
ethertype IPv4 (0x0800), length 74: 192.168.1.1.1183 > desktop-ubuntu.local.www
ethertype ARP (0x0806), length 60: Reply 192.168.1.7 is-at aa:bb:cc:cf:80:99
ethertype IPv4 (0x0800), length 74: 192.168.1.1.1943 > desktop-ubuntu.local.8000
ethertype IPv4 (0x0800), length 74: 192.168.1.1.3765 > desktop-ubuntu.local.http-alt
ethertype IPv4 (0x0800), length 74: 192.168.1.1.3896 > desktop-ubuntu.local.3389
ethertype IPv4 (0x0800), length 92: 192.168.1.1.1141 > desktop-ubuntu.local.netbios-ns: NBT UDP PACKET(137)

メモは、これで終わりです。
VPN越しで自宅に溜めこんだ電子書籍を見るとかにも使えそうなので、色々試す価値がありそうです。

VPN越しで動画を見ようとしている方もいらっしゃいました。4

BUFFALO 節電+USBポート搭載 11n/a&n/g対応 ハイパワー 無線LANルーター AirStation WZR-HP-AG300H BUFFALO 節電+USBポート搭載 11n/a&n/g対応 ハイパワー 無線LANルーター AirStation WZR-HP-AG300H
レビューを見る
欲しいものリストに追加
価格:6619円 在庫あり。powered by Amazon.co.jp
  1. WZR-HP-AG300H自体にメディアサーバ機能があります(別途USB HDD必須)。私は別のNASをメディアサーバにしてるのでよくわかりません。 []
  2. WoLもWebブラウザからボタン一つで実施できます。なお、WoLを使う場合、起動対象マシンのBIOS設定などでWoL(Lan Bootとも言う)を有効にする必要があります。 []
  3. GREパケットを通す=PPTPパススルー有効という理解で良いはず。マルチパススルーする場合は、もっと細かい処理が行われます。詳しく知りたい人は、以下ページの第4回 「PPTPを使用したリモートアクセスVPNの仕組み」を見てください。http://www.tatsuyababa.com/NW-VPN/ []
  4. 私の場合、slingboxでネット越しにテレビを見る環境を構築済みなので、それで満足してます。
    []

NetBeans on windowsでLinux上のrubyプログラムをリモートデバッグするためのproxy

by ginriki | 9 月 27th, 2010 

NetBeansのリモートデバッグについて、以前の記事で、以下のように書きましたが、

注意点としては、リモートと同じフルパス上にソースコードを置く必要がある点です。リモート先で、/var/www/railsprjにRoRのプロジェクトコードが置いてあるなら、ローカルでも/var/www/railsprjにプロジェクトコードを置く必要があります。

このままだと、Windows上のNetBeansでLinux上のRailsがリモートデバッグできません。
そこで、NetBeansとFast Debugger(ruby-debug-ide)の間に挟むproxyを用意して、proxyの中でファイルパス変換することでリモートデバッグできるようにしました。1

せっかくなので、以下にproxyのソースコードを置いておきます(MIT Licenseにしときました)。

以下の構成で、基本的な機能(変数チェック、ステップ実行、breakpointセット辺り)が動作するのは確認しました。
Railsがデバッグできるかどうかは、まだ試してません。

[NetBeans (Debuger GUI)] on Windows
  ↓↑
[proxy] on Windows/Linux
  ↓↑
[ruby process (+ruby-debug-ide)]  on Linux

・CRuby 1.8.7 および JRuby 1.5.1でproxy動作確認した。

なお、実際にデバッグするときは、NetBeansが動くマシン上にもデバッグ対象のソースコードを置かないとソースコードデバッグできません。
例えば、ソースコードは以下のように配置します。

・ruby-debug-ide側
/home/user/script
|---test.rb
\---lib
      \---somelib.rb

・NetBeans側
C:\win_script
|---test.rb
\---lib
      \---somelib.rb


proxyを使ったデバッグの仕方


以下の手順でproxyの起動やNetBeansからproxyへの接続を行えば、後は普通にデバッグできます。

  1. ruby-debug-ide付きでRuby process起動(以前の記事参照)
  2. proxy起動(Linux上で起動するか、NetBeans on Windows上で起動するかはお好みで)。
  3. NetBeansでproxyへの接続。

1, 2の手順はどちらが先でもOKです。
1は以前の記事と同じなので、2, 3の説明だけ書きます。

2. proxy起動

proxyスクリプトをオプション指定して起動するだけです。proxyスクリプトのオプションは以下の通り。

Usage: ruby-debug-ide-proxy.rb -t  [-p listen_port] [--rdbprefix prefix] [--ideprefix prefix] [-d]

Example: ruby-debug-ide-proxy.rb -t localhost --rdbprefix "/home/user/script" --ideprefix "C:\\win_script" -d

Exampleの例を説明すると、

  • localhostのruby-debug-ideに接続して(接続先portは1234。指定するときはlocalhost:6000のようにする。)、
  • proxyを経由してNetBeansにデバッグデータ(XML)を送るときは、/home/user/script –> C:\\win_script への変換を行い、
  • proxyを経由してNetBeansからデバッグコマンド(breakpointセットなど)を指示するときは、C:\\win_script –> /home/user/scriptへの変換を行い、
  • proxy実行時は、DEBUGログを出力する

という指定になります。

3. NetBeansからproxyへの接続

NetBeansのメニューから「デバッグ -> デバッガを接続」を選択し、proxyが動作するホスト名やポート番号を指定して接続してください。
NetBeansからproxyに接続した時、proxyからruby-debug-ideへの接続が自動的に行われます。

正しくデバッガ接続できた場合、以下のようなログがproxyプログラムの端末に出力されます。

I, [2010-09-27T03:43:21.124000 #2240]  INFO -- : proxy will replace /home/user/script with C:\win_script
I, [2010-09-27T03:43:21.468000 #2240]  INFO -- : proxy listens on 0.0.0.0:7000
I, [2010-09-27T03:43:24.530000 #2240]  INFO -- : proxy connects to 192.168.1.7:1234
I, [2010-09-27T03:43:24.530000 #2240]  INFO -- : debug start

後は、普通にNetBeansのデバッガGUIが使えます。
例えば、デバッガGUIからbreakpointをセットすると、以下のログがproxy端末上に出ます。(-dを指定して起動した場合のみ)

D, [2010-09-27T03:43:24.546000 #2240] DEBUG -- : (ide -> proxy) b C:\win_script\test.rb:5
D, [2010-09-27T03:43:24.546000 #2240] DEBUG -- : (proxy -> rdb) b /home/user/script/test.rb:5
D, [2010-09-27T03:43:24.562000 #2240] DEBUG -- : (rdb -> proxy) 
D, [2010-09-27T03:43:24.624000 #2240] DEBUG -- : (proxy -> ide) 

手順は以上です。

Ruby/Railsの場合、デバッガはプログラムの動作理解に使うものというのが私の認識です2。なので、Ruby/Rails全般に慣れていない人がデバッガ利用者だと思うので、手順はもう少し簡潔にしたいですが、そうするにはIDE内を修正するしかないかな・・・。

  1. 本当は、NetBeansのIDE内部でファイルパス変換するように修正するべきだと思いますが、面倒だったのでproxyにしました。 []
  2. デバッグは、printfデバッグの方が効率がいいと思ってます。 []

NetBeansからRuby on Rails(RoR)のリモートデバッグ

by ginriki | 7 月 12th, 2010 

NetBeansには、rubyのデバッガフロントエンドが同梱されています。
今回は、それを使ってRuby on Rails(以下RoR)のリモートデバッグをするためのメモです。

個人的には、RoRが動いてるマシンにsshでログインした後、emacsとrubydb3x.elを使う方がメリットがある1と思いますが、emacsの場合デバッガ用GUIはありません。GUIに慣れてる人はNetBeansの方が楽なので、そういう人向けにまとめます。

なお、NetBeansをインストールしたローカルマシンで、RoRも動かす場合のデバッグ方法は、NetBeansのwikiを参照してください。

まあ、ローカルマシン上でデバッグする場合も、リモートマシンと接続してデバッグする場合もやり方はほとんど変わりません。

どちらの場合も、デバッギプロセス(と同じプロセス上で動くruby-debug-ide)がTCP(デフォルトだと1234)をListenし、Netbeansからそこに接続してデバッグを開始します。Netbeansから接続するデバッギプロセスがlocalhostにいるか、リモート先のホストにいるかの違いしかないです。
接続後、Netbeansからruby-debug-ideへbreakpointをしかける場所などを指示します。

デバッグ方法そのものは、ローカル・リモートともに一緒ですが、リモートのホストに対してローカルのNetBeansを接続してデバッグするには、リモート先のホストで以下の作業が必要です。

  1. rubyインストール (解説略)
  2. RubyGemsインストール (解説略)
  3. RoRインストール・プロジェクト作成 (解説略)
  4. ruby-debug-ideインストール
  5. リモート先のRoRプロジェクトのソースコードを、ローカルにも展開
  6. ruby-debug-ide付きでRoRプロジェクトのWebサーバ起動

4, 5, 6の手順は、あまりネット上で見かけないので順に解説します。

ruby-debug-ideインストール

Linuxだと、gemで簡単にインストールできます。(gcc, ruby-develをインストールしておけば。)
Fedora8だと以下で終わりでした。

$ gem install ruby-debug
$ gem install ruby-debug-ide

Windows(のmswin版ruby)の場合、コンパイル環境の用意が面倒なので、コンパイル済みgemをダウンロードした上でインストールします。2

> gem install -l linecache-0.43-mswin32.gem
> gem install -l ruby-debug-base-0.10.3-mswin32.gem
> del ruby-debug-base-0.10.3-mswin32.gem
> gem install ruby-debug
> gem install ruby-debug-ide -v 0.4.6

コンパイル済みgem (linecache, ruby-debug-base)は以下2つのリンク先から
それぞれダウンロードしてください。

リモート先のRoRプロジェクトのソースコードを、ローカルにも展開

ローカルにもソースコードを置かないと、breakpointで止まった時にNetbeansにソースコードが読み込まれません。
注意点としては、リモートと同じフルパス上にソースコードを置く必要がある点です。リモート先で、/var/www/railsprjにRoRのプロジェクトコードが置いてあるなら、ローカルでも/var/www/railsprjにプロジェクトコードを置く必要があります。3

この辺の挙動理解には、NetBeans wikiのChecking debugger engine functionalityとか、ruby-debug-ide protocolとかを参考にして、実際にruby-debug-ideとtelnetで会話するといいです。

ruby-debug-ide付きでRuby on RailsプロジェクトのWebサーバ起動

以下な感じでRoR Webサーバ起動します。(-dも付けると、NetBeansとの通信時の処理が良く見えます。)

$ rdebug-ide --stop script/server -h 0.0.0.0
Fast Debugger (ruby-debug-ide 0.4.6) listens on 0.0.0.0:1234

NetBeansからruby-debug-ideに接続

NetBeans IDEのメニュー -> デバッグ -> 接続をクリックし、ダイアログにリモート先ホスト名とポート番号(1234)を指定して接続します。
–stopを指定してあるので、RoRの処理先頭でstopするはずです。
あとは、IDEからbreakpoint指定するなり、なんなり自由に操作します。
watch変数として、paramsを追加しておくといい感じです。

  1. sshのポート(TCP 22)が開いてることは普通にありますが、デバッガ・デバッギ通信用のポートがfirewallで遮断されることは良くあります。 []
  2. 最近だと、Visual C++は無料で手に入りますがmswin版rubyをコンパイルしたVisual C++と同じバージョンをインストールする必要しないと「MSC version unmatch」エラーが出てめんどくさいです。http://rubyforge.org/tracker/index.php?func=detail&aid=16774&group_id=1900&atid=7436 []
  3. WindowsとLinuxだとフルパスを一致させることが不可能なので、例えば、Windows上のNetBeansからLinux上のRoRがデバッグができないという点でかなりアレな仕様です。誰もやってないなら修正パッチ作るかな。 []

バッチファイル作成ノウハウ

by ginriki | 6 月 21st, 2010 

Windowsのバッチファイルを作る機会があったので、その時のノウハウメモ。

サービスの状態確認

サービスの起動・停止状態の出力させるには、sc.exe queryが使える。(ただし、Windows XP/Windows Server 2003以降)
後は、findstr (Windows用のgrep)と合わせて使えばいい。

たとえば、タスクスケジューラが起動しているかどうかの確認バッチファイルを
作ると以下の通り。

@echo off

sc.exe query Schedule | findstr STATE | findstr RUNNING > nul
if %ERRORLEVEL% == 1 echo Scheduler isn't running
if %ERRORLEVEL% == 0 echo Scheduler running

実行結果(タスクスケジューラ起動時)は以下の通り。

C:\>service_check.bat
Scheduler running

上のバッチファイルのコツは、

  • 無駄なecho表示を消すこと(@echo off)
  • findstrの出力をnull deviceに出力させること( > nul)
  • findstrのマッチ結果を%ERRORLEVEL%で調べること

ちなみに、nulというnull device名の歴史をたどると、古い順に、

PIP > CP/M > MS-DOS > Windows

という流れで受け継がれてます。1

バッチファイル内の特殊記号

バッチファイル上で、特殊記号として扱われるものがいくつかあります。
例えば、括弧()が特殊記号です。このような記号を普通の文字として扱いたいときは、^を特殊記号の前に付ける必要があります。

例は以下の通り。

@echo off
if 1 == 1 (
  echo foo ^(bar^) baz
)
C:\>with_hat.bat
foo (bar) baz

ちなみに^を抜くと、if文の終端(=echoの終わり)がbarの直後と判断されてしまい、
その後のbazをコマンドとみなして実行するためエラーになります。

C:\>no_hat.bat
baz の使い方が誤っています。

他の特殊記号については、このWikiを参照してください。

  1. PIPのWikipediaページとか、MSDNの予約ファイル名の説明とか参照。 []

色々なtarフォーマットの確認方法

in CentOS, tar
by ginriki | 6 月 14th, 2010 

tarファイルには、いくつかのフォーマットがあるのだけど、何らかのコマンドで作成したtarファイルが
どのフォーマットで作られているか判断するやり方が見当たらないのでメモしとく。

なお、メジャーなフォーマットは以下らしい1

  • POSIX.1-1988 ustar format
  • GNU tar format
  • POSIX.1-2001 pax format

利用者から見たフォーマット間の大きな違いは、ustar形式のtarフォーマットの場合、tarでまとめる各ファイルのファイル名が256文字制限だったり、各ファイルのサイズ制限が8GBになってること。残りの2つのフォーマットは制限なし。その代わり、他と比べてustar形式は歴史がある形式なので、対応しているツールも多い。

ちなみに、普通のlinux上のtarプログラム(=GNU tar)がデフォルトで使うtarフォーマットは–helpを付ければわかる。
以下のように、最近のtarプログラムならgnuフォーマットのはず。

# tar --help
[...略]
*This* tar defaults to:
--format=gnu -f- -b20 --quoting-style=escape --rmt-command=/sbin/rmt


・tarファイルのフォーマットの見分け方

hexdumpコマンドでtarファイルを出力し、tarファイル内にアーカイブされた各ファイルのtarヘッダ(とpaxヘッダ)で見分けます。2

hexdump -C で出力された情報のなかで、
それぞれのフォーマットごとに特徴的なヘッダ部分は以下。

  • ustar format
    00000100  ...(省略)...  |.ustar.00root...|
    
  • GNU tar format
    00000100  ...(省略)...  |.ustar  .root...|
    
  • pax format
    00000000  ...(省略)...  |./PaxHeaders.108|
    

以下、各フォーマットの確認例です。

# echo "hello" > foo.txt
# tar --format=gnu -cvf test.gnu.tar foo.txt
# tar --format=ustar -cvf test.ustar.tar foo.txt
# tar --format=pax -cvf test.pax.tar foo.txt

# hexdump -C test.ustar.tar
00000000  66 6f 6f 2e 74 78 74 00  00 00 00 00 00 00 00 00  |foo.txt.........|
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000060  00 00 00 00 30 30 30 30  36 34 34 00 30 30 30 30  |....0000644.0000|
00000070  30 30 30 00 30 30 30 30  30 30 30 00 30 30 30 30  |000.0000000.0000|
00000080  30 30 30 30 30 30 36 00  31 31 34 30 35 31 37 34  |0000006.11405174|
00000090  33 30 34 00 30 31 32 33  37 33 00 20 30 00 00 00  |304.012373. 0...|
000000a0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000100  00 75 73 74 61 72 00 30  30 72 6f 6f 74 00 00 00  |.ustar.00root...|
00000110  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000120  00 00 00 00 00 00 00 00  00 72 6f 6f 74 00 00 00  |.........root...|
00000130  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000140  00 00 00 00 00 00 00 00  00 30 30 30 30 30 30 30  |.........0000000|
00000150  00 30 30 30 30 30 30 30  00 00 00 00 00 00 00 00  |.0000000........|
00000160  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000200  68 65 6c 6c 6f 0a 00 00  00 00 00 00 00 00 00 00  |hello...........|
00000210  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00002800

# hexdump -C test.gnu.tar
00000000  66 6f 6f 2e 74 78 74 00  00 00 00 00 00 00 00 00  |foo.txt.........|
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000060  00 00 00 00 30 30 30 30  36 34 34 00 30 30 30 30  |....0000644.0000|
00000070  30 30 30 00 30 30 30 30  30 30 30 00 30 30 30 30  |000.0000000.0000|
00000080  30 30 30 30 30 30 36 00  31 31 34 30 35 31 37 34  |0000006.11405174|
00000090  33 30 34 00 30 31 31 30  37 33 00 20 30 00 00 00  |304.011073. 0...|
000000a0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000100  00 75 73 74 61 72 20 20  00 72 6f 6f 74 00 00 00  |.ustar  .root...|
00000110  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000120  00 00 00 00 00 00 00 00  00 72 6f 6f 74 00 00 00  |.........root...|
00000130  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000200  68 65 6c 6c 6f 0a 00 00  00 00 00 00 00 00 00 00  |hello...........|
00000210  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00002800

# hexdump -C test.pax.tar
00000000  2e 2f 50 61 78 48 65 61  64 65 72 73 2e 31 30 38  |./PaxHeaders.108|
00000010  31 2f 66 6f 6f 2e 74 78  74 00 00 00 00 00 00 00  |1/foo.txt.......|
00000020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000060  00 00 00 00 30 30 30 30  36 34 34 00 30 30 30 30  |....0000644.0000|
00000070  30 30 30 00 30 30 30 30  30 30 30 00 30 30 30 30  |000.0000000.0000|
00000080  30 30 30 30 30 35 30 00  31 31 34 30 35 31 37 34  |0000050.11405174|
00000090  34 30 31 00 30 31 33 34  34 31 00 20 78 00 00 00  |401.013441. x...|
000000a0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000100  00 75 73 74 61 72 00 30  30 00 00 00 00 00 00 00  |.ustar.00.......|
00000110  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000140  00 00 00 00 00 00 00 00  00 30 30 30 30 30 30 30  |.........0000000|
00000150  00 30 30 30 30 30 30 30  00 00 00 00 00 00 00 00  |.0000000........|
00000160  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000200  32 30 20 61 74 69 6d 65  3d 31 32 37 36 34 34 32  |20 atime=1276442|
00000210  38 37 30 0a 32 30 20 63  74 69 6d 65 3d 31 32 37  |870.20 ctime=127|
00000220  36 34 34 32 38 32 30 0a  00 00 00 00 00 00 00 00  |6442820.........|
00000230  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000400  66 6f 6f 2e 74 78 74 00  00 00 00 00 00 00 00 00  |foo.txt.........|
00000410  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000460  00 00 00 00 30 30 30 30  36 34 34 00 30 30 30 30  |....0000644.0000|
00000470  30 30 30 00 30 30 30 30  30 30 30 00 30 30 30 30  |000.0000000.0000|
00000480  30 30 30 30 30 30 36 00  31 31 34 30 35 31 37 34  |0000006.11405174|
00000490  33 30 34 00 30 31 32 33  37 33 00 20 30 00 00 00  |304.012373. 0...|
000004a0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000500  00 75 73 74 61 72 00 30  30 72 6f 6f 74 00 00 00  |.ustar.00root...|
00000510  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000520  00 00 00 00 00 00 00 00  00 72 6f 6f 74 00 00 00  |.........root...|
00000530  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000540  00 00 00 00 00 00 00 00  00 30 30 30 30 30 30 30  |.........0000000|
00000550  00 30 30 30 30 30 30 30  00 00 00 00 00 00 00 00  |.0000000........|
00000560  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000600  68 65 6c 6c 6f 0a 00 00  00 00 00 00 00 00 00 00  |hello...........|
00000610  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00002800
  1. Pythonのtarfileライブラリのマニュアル参照。 []
  2. ヘッダの概説はFreeBSDのtarマニュアルがわかりやすいです。もっと知りたい場合は各tar形式の仕様書を見た方が良いです。 []

Ruby/Jrubyの実行時フックの低レベルAPI

in Java, Ruby
by ginriki | 4 月 8th, 2010 

メモ。

RubyやJRubyには、特定の条件(コード行が変化したとか、メソッドを呼び出したなど)を満たした時に呼び出されるフック処理を定義することができます。

Ruby-1.8の低レベルなフック追加API(=C言語API)は、
thread.cのrb_add_event_hook関数。シグネチャは以下。

  • void rb_add_event_hook(rb_event_hook_func_t func, rb_event_flag_t events, VALUE data);

JRuby-1.4.0で上記関数にほぼ対応するのは、Ruby.java
Ruby.addEventHookメソッド。シグネチャは以下。

  • public void addEventHook(EventHook hook)

引数の型であるEventHookやその継承クラスは以下参照。

フックAPIの挙動についての仕様書って、どこにもない感じだなあ。

JRuby実行中にjdbでアタッチしてrubyプログラムのbacktrace

in JDB, Ruby
by ginriki | 1 月 12th, 2010 

Ruby自身のデバッガだと、任意のタイミングでアタッチしてbacktrace見るといったことができない(はず)なので、もっと低レベルのデバッガを利用して、それを実現する方法についてメモ。今回はJRubyに対してjdbでアタッチ&backtraceする方法についてのメモです。
なお、CRubyの場合は、以前の記事とか、さらにその先のリンク先を見れば良いです。
ちなみに、今回試したJRubyのversionは以下の通り。OSはCentOS 5.3です。

$ jruby --version
jruby 1.3.1 (ruby 1.8.6p287) (2009-06-15 2fd6c3d) (Java HotSpot(TM) Client VM 1.6.0_16) [i386-java]


jdbによるbacktrace



まず、アタッチ対象例のRubyコードとして以下を用意。

sample.rb

RUBY:
  1. def foo
  2.   p "abc"
  3.   bar
  4. end
  5.  
  6. def bar
  7.   p "foge"
  8.   sleep(1000)
  9. end
  10.  
  11. abc

そして、デバッガattach用のポートを開けて起動します1

$ jruby -J-Xdebug -J-Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n sample.rb

barメソッド内のsleepの実行をしているうちに、別のターミナルを開いてjdbでアタッチし、スレッドを停止させます。

$ jdb -attach 8000
> suspend
すべてのスレッドが中断されました

そして、backtraceを表示させます。以下の方法で大体表示できます(いい加減なやり方ですが)。

> threads
グループ system:
  (java.lang.ref.Reference$ReferenceHandler)0x9db Reference Handler 状況待機中
  (java.lang.ref.Finalizer$FinalizerThread)0x9da  Finalizer         状況待機中
  (java.lang.Thread)0x9d9                         Signal Dispatcher 実行中
グループ main:
  (java.lang.Thread)0x1                           main              状況待機中
> thread 0x1
main[1] where
  [1] java.lang.Object.wait (ネイティブ メソッド)
  [2] org.jruby.RubyThread.sleep (RubyThread.java:718)
  [3] org.jruby.RubyKernel.sleep (RubyKernel.java:725)
  [4] org.jruby.RubyKernel$s_method_0_1$RUBYINVOKER$sleep.call (null)
  [5] org.jruby.internal.runtime.methods.JavaMethod$JavaMethodN.call (JavaMethod.java:620)
  ...(以下略)
main[1] up 4
main[5] locals
メソッド引数:
context = instance of org.jruby.runtime.ThreadContext(id=2527)
self = instance of org.jruby.RubyObject(id=2528)
clazz = instance of org.jruby.MetaClass(id=2529)
name = "sleep"
arg0 = instance of org.jruby.RubyFixnum(id=2531)
block = instance of org.jruby.runtime.Block(id=2532)
ローカル変数:
main[5] dump context.frameIndex
 context.frameIndex = 2 #frameStackの上限(たぶん)
main[5] dump context.file + ":" + context.line
 context.file + ":" + context.line = "sample.rb:7" #現在位置
main[5] dump context.frameStack[2].fileName + ": " + context.frameStack[2].name + ":" + context.frameStack[2].line
 ...(省略)... = "sample.rb: bar:2" #1つ前のスタックフレーム
main[5] dump context.frameStack[1].fileName + ": " + context.frameStack[1].name + ":" + context.frameStack[1].line
 ...(省略)... = "sample.rb: foo:10" #2つ前のスタックフレーム
main[5] dump context.frameStack[0].fileName + ": " + context.frameStack[0].name + ":" + context.frameStack[0].line
 ...(省略)... = ": null:0" #トップレベル

以上になります。

  1. ここの「Debugger」の項目を参考にオプション設定しました。 []

autoloadで自動読み込みされないファイルをユーザアクセス毎に再読み込みする

by ginriki | 12 月 31st, 2009 

今回はRuby on Railsについてメモ。

autoloadで自動読み込みされないファイル1をcontrollerの中などでrequireを使って読み込んだ場合、そのファイルはdevelopmentモード時でもユーザアクセス毎に再読み込みされません。そのため、ファイルを編集してもwebサーバを再起動しないと変更が反映されないので、デバッグがやりづらくて困ってました。

解決策がないかと思って、ググってみたところ、以下のページに解決策が書いてありました。
http://www.pistolfly.jp/weblog/2007/06/require-dependency.html

requireじゃなくて、require_dependencyでファイル読み込みすれば良いということでした。
require_dependencyの挙動については、上のリンク先を参照してください。
この辺り(autoload周辺)って、Ruby on Rails公式なドキュメントが見当たらないですねえ。・・・実装を読めってことか。

以下、問題のコード例と解決方法。(ruby 1.8.5, rails 2.3.3, WEBrick 1.3.1で実験しました。)

ファイル読み込みの対象となるファイルを用意します。

<rails_project>/lib/hoge_fuga.rb

RUBY:
  1. class Hoge
  2.   def hoge
  3.     return "abc"
  4.   end
  5. end

上のファイルを読み込むcontrollerを用意します。
このとき、requireの代わりにrequire_dependencyでファイルを読み込みます。

<rails_project>/app/controllers/test_controller.rb

RUBY:
  1. #require 'hoge_fuga'
  2. require_dependency 'hoge_fuga'
  3.  
  4. class TestController <ApplicationController
  5.   def hoge
  6.     a = Hoge.new
  7.     render(:text => a.hoge)
  8.   end
  9. end

んで、Webサーバを起動。

$ cd <rails_project>
$ ruby script/server

で、ブラウザからtest/hogeにアクセスすると、ブラウザ上にabcが出力されます。
この状況で、<rails_project>/lib/hoge_fuga.rbを以下のように書き換えてみます。

RUBY:
  1. class Hoge
  2.   def hoge
  3.     return "123"
  4.   end
  5. end

変更を保存した後、ブラウザからtest/hogeにアクセスすると、ブラウザ上に123が出力されます。
確かに、Webサーバを再起動しなくても、<rails_project>/lib/hoge_fuga.rbが再読み込みされてますね。

これでデバッグがしやすくなるなあ。

  1. Railsの場合、クラス定義と定義が記述されたファイルの名前が対応づいてないとautoloadで自動読み込みされません(たぶん)。例えば、FooBarというクラスの定義はfoo_bar.rbというファイルの中で行われてないとautoloadにされません。 []

Hyper Estraierで文書検索 (+ CentOS用rpm作成)

by ginriki | 12 月 7th, 2009 

扱う文章の数が膨大になって管理しづらくなってきたので、Hyper Estraierの導入を検討しました。せっかくなので導入手順をメモ。
ちなみに、自分のローカルマシン(Windows)内の文書を管理したいだけなら、Hyper Estraierをエンジンに積んだDesktopHEを使えば十分と思います。1

FedoraやVine LinuxだとEstraierのrpmパッケージが公式に存在するので、yumとかapt-getでインストールすればいいのですが、CentOSにはrpmパッケージがありません。

調べてみると、Vine Linuxのsrpmを使うと楽ということなので、srpmからrpmbuildします。2

まず、Vine LinuxのSRPMリポジトリからqdbm,mecab,mecab-ipadic,hyperestraierをダウンロードします。
その後、以下のようにrpmbuild & install (rpmbuildなどは、yum install rpm-buildなどで入れておくこと)。

# rpmbuild --rebuild qdbm-1.8.77-1vl5.src.rpm
# rpm -ivh /usr/src/redhat/RPMS/[arch]/qdbm-1.8.77-1.i386.rpm qdbm-devel-1.8.77-1.i386.rpm

# rpmbuild --rebuild mecab-0.97-2vl5.src.rpm
# rpm -ivh /usr/src/redhat/RPMS/[arch]/mecab-0.97-2.i386.rpm mecab-devel-0.97-2.i386.rpm

# rpmbuild --rebuild mecab-ipadic-2.7.0.20070801-1vl5.src.rpm
# rpm -ivh /usr/src/redhat/RPMS/[arch]/mecab-ipadic-2.7.0.20070801-1.i386.rpm

# rpmbuild --rebuild hyperestraier-1.4.13-2vl5.src.rpm
# rpm -ivh /usr/src/redhat/RPMS/[arch]/hyperestraier-1.4.13-2.i386.rpm hyperestraier-perl-1.4.13-2.i386.rpm

これでインストールは完了。

インデックスの作成方法は以下ページ参照。

/home/user/doc以下の文書をインデックス化して、/var/estraier/indexにインデックス作成する場合のコマンド例は以下。

$ estcmd gather -il ja -sd /var/estraier/index /home/user/doc

後は、検索用のWebページを用意すれば終わり。CentOSの場合は以下の通り。

# yum install httpd
# service httpd start
# cp /usr/libexec/estseek.cgi /var/www/cgi-bin/.
# /usr/share/hyperestraier/estseek.* /var/www/cgi-bin/.

上記を実行した後、/var/www/cgi-bin/estseek.confのindexnameの項目を編集します。

indexname: /var/estraier/index
...省略

手元のブラウザから、estseek.cgiへアクセスすると検索ページが見れます。
もしこの時、「Error: the index is missing or broken.」とか出てくるようなら、indexファイルへのアクセス権がapacheにないせいかもしれません。
estseek.conf内の項目の意味は、Hyper Estraierのユーザマニュアルを参照してください。

  1. でも、index作成がなんか遅い気がする。まあ、気のせいかも []
  2. だれかCentOS用hyperestraierのyumリポジトリ作ってくれませんか?(他力本願) []

rubyプロセスのコアファイルからバックトレースする。(おまけ: python)

by ginriki | 11 月 9th, 2009 

某所で、「rubyプロセスがSEGVとかした時のコアファイルから、rubyスクリプトのバックトレースは取れるの?」って聞かれたので、ちょっと調べてみました。

結論としては、「rubyメソッドの呼び出し位置(ファイル名, 行番号)は取れるけど、呼び出し時の実引数を見るのは難しい」っていう感じです。
rubyのスタックフレームは、当然、rubyプロセスのメモリ上に構築されるので、スタックフレームのデータ構造さえわかれば、ある程度は表示できます。

ただ、コアファイルを出力したrubyプロセスはすでに存在しないので、ruby実装に使われているC関数をデバッガで(正確にはrubyプロセス上で)実行することができません。
そのため、オブジェクトのinspectなどを実行することが難しく、実引数のオブジェクトが何か調べることが困難です。ということで、今回はスタックフレームを表示させる方法を以下で説明します。

rubyスタックフレームを表示するGDBスクリプトは以下になります(rb_dump.gdb)。
(moriyoshiさんのブログ「 GDBで実行中のスクリプト言語のスタックフレームをダンプしてみる試み」のコードをほとんどそのまま使わせていただきました。ありがとうございます。)

CODE:
  1. define dump_rb_bt_from_core
  2.   set $t = ruby_frame
  3.   while $t
  4.     printf "[0x%08x] ", $t
  5.     if $t->node.nd_file
  6.       printf "(%s:%d)\n", $t->node.nd_file, ($t->node.flags>> 19) & ((1 <<(sizeof(NODE*) * 8 - 19)) - 1)
  7.     else
  8.       printf "(UNKNOWN)\n"
  9.     end
  10.     set $t = $t->prev
  11.   end
  12. end
  13.  
  14. document dump_rb_bt_from_core
  15.   dumps the current frame stack from core file. usage: dump_rb_bt_from_core
  16. end

今回はサンプルプログラムとして、0アドレスにアクセスするC拡張ライブラリを実行するrubyスクリプトを用意します。
まず、C拡張ライブラリ(segv.c)、

C:
  1. #include "ruby.h"
  2.  
  3. VALUE do_segv(VALUE self){
  4.         *(char*)(0x0) = 0;
  5.         return Qnil;
  6. }
  7.  
  8. void Init_segv(){
  9.         VALUE module;
  10.         module = rb_define_module("Segv");
  11.         rb_define_module_function(module, "do_segv", &do_segv, 0);
  12. }

で、後は適当にextconf.rbを作って、segv.soを作ります。

次にsegv.soを呼び出すRubyスクリプト(test.rb)。

RUBY:
  1. require 'segv'
  2.  
  3. def test1(a)
  4.   test2(a)
  5. end
  6.  
  7. def test2(b)
  8.   Segv::do_segv
  9. end
  10.  
  11. test1(1)

さて、サンプルプログラムの用意ができたので、SEGVさせてみます。

$ ulimit -c unlimited
$ ruby test.rb
test.rb:8: [BUG] Segmentation fault
ruby 1.8.5 (2006-08-25) [i386-linux]

アボートしました (core dumped)

これでコアファイルが出力されたので、それをGDBで解析します。

今回はCentOS 5.3のyumでインストールしたrubyから出力されたコアファイルなので、
GDBで解析するには、ここからrubyのdebuginfoをインストールしておく必要があります。

$ wget http://debuginfo.centos.org/5/i386/ruby-debuginfo-1.8.5-5.el5_3.7.i386.rpm
# rpm -i ruby-debuginfo-1.8.5-5.el5_3.7.i386.rpm

それでは、GDBでコアファイルからrubyのバックトレースをさせてみます。

$ gdb ruby core.29443
GNU gdb Fedora (6.8-37.el5)
...
(gdb) backtrace   #通常のC関数レベルのバックトレース。
#0  0x00b3a402 in __kernel_vsyscall ()
#1  0x0056fdf0 in raise () from /lib/libc.so.6
#2  0x00571701 in abort () from /lib/libc.so.6
#3  0x00c514c2 in rb_bug (fmt=) at error.c:214
#4  0x00cbd80b in sigsegv (sig=) at signal.c:537
#5  [signal handler called]
#6  do_segv (self=3086662140) at segv.c:4
#7  0x00c54dd5 in call_cfunc (...) at eval.c:5657
#8  0x00c5c4ab in rb_call0 (...) at eval.c:5810

(gdb) source rb_dump.gdb      #この記事の最初に作ったGDBスクリプトをロード
(gdb) dump_rb_bt_from_core  # rubyバックトレース
[0xbfc1da20] (test.rb:8)
[0xbfc1e0e0] (test.rb:4)
[0xbfc1e7c0] (test.rb:11)
[0x00d0f960] Cannot access memory at address 0x4

以上になります。

pythonもコアファイルからスタックフレームが取れるか調べてみましたが、
DebuggingWithGDBのgdbinitスクリプトを読む限り、
rubyと同様に実行していたpythonスクリプトのファイル名と行番号は取れそうです。

ブログで紹介した商品

  • Image of デバッガの理論と実装 (ASCII SOFTWARE SCIENCE Language)