« Visual Studio 2008 と Windows 98 | メイン | 機種リスト »

2010年04月17日

吉里吉里 その他の開発日誌:: スクリプトデバッガー

    

先週の土曜日 ( ちょうど一週間前 ) からスクリプト言語用のソースレベルデバッガを作っている。
スクリプトデバッガー

ネイティブなデバッガの場合、ブレークポイントはコードを書き換えて、ブレークポイント例外を発生させる命令に置き換えるので、スクリプトの場合も同じようにバイトコードを書き換えてやればいいのかなぁと漠然と考えていたんだけど、Squirrel のデバッグ用 API を見てそんな面倒なことしなくてもいいことに気付いた。

基本的な API は単純で以下のようなコールバック関数を登録できる。
debughook( event_type, sourcefile, line, funcname )
event_type は、各行の実行時、関数の呼び出し時、関数の終了時 の3種。
この API を見た瞬間どうすればいいのかわかる。

ブレークポイントで止めたければ、sourcefile と line を見て、ブレークポイントが設定されているものと同じであれば止めればよい。
ステップ実行は、各行の実行時ごとに止め、関数呼び出しなどが発生した場合は、ネストをカウントして、リターン時に減算、元の深さに戻ってきたら、またステップを進めればよい。
トレース実行はさらに簡単で、3種のイベントどれでも止めてしまえばよい。

ブレークやステップ実行はこれで楽勝。
と言うこととで、吉里吉里2のソースを追って同じようなコールバック関数を登録できるようにする。
これをプラグイン用に公開…… と作っていたんだけど、本体の方に手を入れまくるんだから、わざわざプラグインにする意味はないかと本体に組み込んでしまった。
そして、固定位置にブレークポイントを置いて正しく機能するか確認。
ファイル名と行番号で行が切り替るごとに判定すると言うやや力業な方法なので、負荷増が気になっていたが気にするほどでもなかった。
まあ、画像等に比べればテキスト処理で負荷が気になることは変なことしなければあまりないか。
そんなに遅いマシンを開発用に使っていることはあまりないだろうし。

デバッガとデバッギ(吉里吉里2)は、扱いやすさを考えて別プロセス(アプリ)で作ると決めていた。
作るのは同じプロセスの方が簡単だけれど、そのプロセスが終了するごとに設定とかいろいろとやり直しになるのは面倒この上ない。
普通のデバッガのように「実行」ってしたら起動して動いて欲しい。
そのようにするためには、プロセス間で通信する必要がある。
はじめはお互いにイベントを投げ合えばそれでいいと考え、そのように実装したがすぐに問題に気付く。
吉里吉里2は、メインのメッセージループが回るよりも先にスクリプトが実行される。
でも、そんなこと気にせずメッセージを取得しようと試みたが、どうもうまく受信できない。
と言うことで、早々にこの通信方法はあきらめる。
この方法には他にも問題があると認識していたのもある。
その問題とは、停止していると言ってもメッセージループは回しているので、別スレッドは動いている。
つまり、サウンドやムービーが停止中も進んで行ってしまう。
これは、余り好ましくない。

通信関係よりも前に、面倒が起きないように、デバッグ対象のプロセスを、デバッグ用に起動して、デバッグイベントを受け取るようにしていたので、デバッギでブレークポイント例外を発生させて止めて、その後何かを使って要求を送れば解決できる。
要求とは、ステップ実行などの種類やブレークポイントの位置。
ブレークポイントかどうかはデバッギ側が判断するので、デバッギに送ってやる必要がある。
が、停止しているプロセスにどうやって要求を送るのか? が問題。
Windows のデバッグ用API にはデバッグ対象プロセスのメモリを読み書き出来るので、これを使うのが本節だろうけど、どうやってアドレスを得るのか?
いろいろ考えた結果、デバッギ側に通信用のメモリをあらかじめ確保しておいて、それをデバッガ側に伝えてもらって、以降はそのアドレスに書き込んで通信することにした。
デバッギ側からデバッガ側への通信は普通にメッセージで送れるので、そのようにした。
どこで停止したかやコールスタック、変数の情報などもメッセージで送ってしまう。
メモリが読み書き出来るので、頑張ればたどれるけど大変なのでデバッギ側に教えもらう。

これで後は作り込めばOK
ただ、吉里吉里2ソースを追って、ローカル変数の取得方法やメンバ変数の取得方法などを調べて、取得のための機能を追加するなどが大変だった。
吉里吉里2は、コンパイルが終わるとローカル変数の名前は破棄してしまっていて、レジスタしかわからない。
だから、名前とレジスタ、スコープを記憶して、現在のスコープの変数とそのレジスタ位置から値を得なければいけない。
スコープの単位は、ブロックごとにしてもいいけどそこまでしなくても関数単位で十分なので、そのようにした。
スコープは、クラス名、関数名、ファイル名、バイトコードオフセットで判断。
クラス名等の名前は、ユニークなIDになるようにして保持。
ソースにするとこんな感じ。
struct ScopeKey { int classindex_, funcindex_, fileindex_, codeoffset_; };
このキーをmapに入れて、その値に変数のリストを入れる。
operator < の判定は、classindex_、funcindex_、fileindex_、codeoffset_ の順。
つまり、内部的にはクラス順、クラス内は関数順…… と並ぶ。
ScopeKey を使ってそのローカル変数を取り出すことは普通に出来る(そもそもそれが目的)し、クラスの中の全ローカル変数や、関数の中のローカル変数を取得することも出来なくはない。
不要だけれども。
まあ、クラスのメンバ変数を funcindex_ に -2 を入れて……等のルールを決めれば、これでクラスのメンバ変数も管理できるが、別管理で実装した。
後、スコープをブロック単位で管理しようとしたら、codeoffset_ をブロックごとに持てばよい。
上位ブロックの変数を得ようと思えば…… ってダメじゃん。
この管理構造で対応出来ると思っていたけど、欠陥に気付いた。
まあ、関数単位で管理することにしたからいいか。
ScopeKey はなかなかいい思いつきと思ったけど、全然そんなこと無かった。
話が大きくそれたけれど、基本機能はこれでほとんど実装できた。
残りは使いやすくするためのデバッガの細かい機能類。

TJS2 対応のデバッガが一段落したら、Squirrel に対応する予定。
PC 上の別プロセスのみでなく、リモートというか、ゲーム機のスクリプトのデバッグに対応して、しばらくはそれメインに使うはず。

スクリプトでもデバッガが使えると便利だろうなぁ、欲しいなぁと常々思っていたけれど、実装に動いているのを見るとかなり便利そう。
スクリプトのデバッグは、修正の容易さと記述能力の高さに頼って printf デバッグが基本だったけれど、デバッガで処理の流れを追えるとどう動いているのかわかりやすい。
しかし、これはいいなぁ。
普通に売れるよね? 自分だったら売ってたら買う。とか思ってしまう(吉里吉里用は普通にフリーで公開予定)。
スクリプトデバッガ自体は、比較的汎用的に作っているので、他の環境へのポーティングもそう大変ではないはずなので、他の環境で作る用になった時は、まずはこれをポーティングして…… となりそうだ。



投稿者 Takenori : 2010年04月17日 17:38



コメント

はじめまして。
とても便利そうなモノ作ってますね。
ぜひ使いたいのですが、これは公開していないのでしょうか?

投稿者 Anonymous : 2012年03月16日 08:51

こんにちは。
公式のリポジトリにソースとバイナリの両方が入っています。

投稿者 Takenori : 2012年03月16日 17:48

ありがとうございます。
早速使用してみたのですが
ブレークポイントを素通りしてしまいます。

別のエントリに

>ただ、そのままのビルドでは有効にならずデバッグオプション
>「ENABLE_DEBUGGER」を付けてコアをビルドしたら有効になる。

とあるのですが、これは吉里吉里本体ソースコードを
自分でビルドし直す必要があるということでしょうか?

また、使い方や注意事項などをまとめたドキュメントはありますか?
ひと通り探したのですが見つけられませんでした。
よろしくおねがいします。

--
Windows7 64bit
krkr.eXe 2.32.2.426
MD5: 5c8f173ffd06a8d6070f5b68e1016e31

投稿者 Anonymous : 2012年03月17日 07:13

こんにちは。
>自分でビルドし直す必要があるということでしょうか?
デバッガ用のビルド済みバイナリは現在配布されていないので、自分でビルドする必要があります。

ドキュメント等は特にありません。

投稿者 Takenori : 2012年03月17日 15:19

手元にビルド環境ないのですぐに試せませんが
近いうちにチャレンジしてみます。
ありがとうございました。

投稿者 Anonymous : 2012年03月17日 16:14


comments powered by Disqus
Total : Today : Yesterday : なかのひと