Luaとデバッグ
みなさんLuaを使ったことはありますか?
私は元々JavaScriptを書くことが多かったのですが,最近はよくLuaのコードを書いています。JavaScriptと比べて,私はLuaの方が好きです。metatable
やコルーチンは魅力的だし,後述するdebug
ライブラリも強力です。しかし,実際の開発を行うとなると,言語仕様や標準ライブラリだけではなくその周辺のツールも重要になってきます。
JavaScriptでWebアプリを開発する際,多くの方がChromeのDev Toolを使っているでしょう。
Dev Toolを使えば,強力なJavaScriptのデバッガやプロファイラを使うことができます。
また,Chrome Extentionを使えばさらに,通信の内容をデバッグ用に変更したりと,さまざまなことができます。
一方,Luaではどうでしょう?一応,ちらほらデバッグツールを見かけますが,環境に依存していたり,機能が貧弱だったりします。Luaの場合,通常,単体で使うのではなくホスト環境があり,一部の機能をスクリプティングするために仕様することが多いでしょう。そのため,環境に依存したり,特定のライブラリに依存していると非常に使いづらいかと思います。
そこで,夏休みにピュアLuaでLUPEという名前のLuaのデバッガを作りました。そして,先日公開することができたため,この記事ではLUPEの機能と仕組みを説明したいと思います。
LUPEの機能
LUPEは以下のリポジトリで公開しています。
LUPEが提供する主な機能は以下のとおりです。
- ブレークポイント(追加・削除・一覧表示)
- ステップ実行(ステップイン・ステップアウト・ステップオーバー)
- 現在行周辺のコードの表示
- ローカル変数一覧の表示
- 式(チャンク)の評価
- ローカル変数とグローバル変数の宣言位置の推測
- ウォッチ式
- 簡易プロファイラ
- コルーチン上でのデバッグ
LUPEはすべてLuaで書かれており,標準ライブラリのみを使用しています。そのため,ほとんどホスト環境に影響されずに,標準入出力が使える環境であれば使用できます。また,標準入出力が使えない場合も入出力方法を変えることで,デバッグすることは可能です。
細かい使い方はREADMEを読んでもらうとして,ここでは基本的な使い方を説明します。
まず,lupe.lua
をいずれかの方法で読み込みます。通常はrequire
を使うと思いますが,ホスト環境が用意する関数にluaファイルを読み込む機能があればそれを使用することも可能です。lupe.lua
を読み込むと,Lupe
というオブジェクトがグローバル変数として定義されるので,そのオブジェクトstart
というメソッドを呼びましょう。start
メソッドは後述するdebug.sethook
を使って,処理の途中で止まれるようにフックを仕込みます。
require('lupe')
Lupe:start()
つぎに,処理を止めたい箇所でLupe()
を呼び出します。
function hoge()
-- some code
Lupe() -- ここで止まる
end
Lupe()
は,実行した行で処理を中断し,デバッグコマンドを受け付けるプロンプトを表示します。
LUPE>
使用できるデバッグコマンドは以下のとおりです。
コメンド名 | 省略形 | 説明 |
---|---|---|
addBreakPoint | ab | ブレークポイントの追加 |
removeBreakPoint | rb | ブレークポイントの削除 |
breakPointList | bl | ブレークポイントの一覧 |
step | s | ステップオーバー |
stepIn | si | ステップイン |
stepOut | so | ステップアウト |
run | 継続 | |
list | l | ソースコードの表示 |
vars | v | ローカル変数の表示 |
definedLine | d | 変数の宣言位置の推測 |
setWatch | sw | ウォッチ式の設定 |
removeWatch | rw | ウォッチ式の削除 |
watch | w | ウォッチ式の表示 |
上記以外 | チャンクの評価 |
それでは,以下のコードをデバッガで追ってみましょう。
require("lupe")
Lupe:start()
local a = 100
function hoge()
local b = 200
Lupe()
fuga()
end
function fuga()
local c = 200
print(c)
end
hoge()
luaコマンドで実行してみると,9行目で処理が止まります。
$ lua sample.lua
stop at @sample.lua:9
LUPE>l
6: function hoge()
7: local b = 200
8: Lupe()
> 9: fuga()
10: end
11:
12: function fuga()
13行目にブレークポイントを仕掛けてみます。
LUPE>ab 13
LUPE>bl
@sample.lua:13
LUPE>l 4
5:
6: function hoge()
7: local b = 200
8: Lupe()
> 9: fuga()
10: end
11:
12: function fuga()
*13: local c = 200
そして,中断した処理を継続させます。
LUPE>run
stop at @sample.lua:13
LUPE>l
10: end
11:
12: function fuga()
>*13: local c = 200
14: print(c)
15: end
16:
1ステップ進めて,変数の一覧を表示してみます。
LUPE>s
stop at @sample.lua:14
LUPE>v
c(number): 200
変数に代入してみましょう。
LUPE>c=300
LUPE>run
300
c
の中身がうまく変わっていますね。
debugライブラリ
Luaにはdebug
ライブラリという強力なデバッグ用の強力な標準ライブラリが存在します。このライブラリを使えば,行や関数呼び出しごとにフック関数を仕掛けたり,現在の行で宣言されているローカル変数等を取得することができます。LUPEではその機能を使って,デバッガの機能を提供しています。
LUPEで使用しているdebug
ライブラリの機能について簡単に説明します。
debug.sethook
debug.sethook
は,行,関数の呼び出し,関数からリターンごとに呼び出されるフックを仕掛けることができます。LUPEでは,この機能を使ってデバッグ用のコールスタックに,関数呼び出しごとのコンテキストを記録しています。変数の状態などは,行ごとに更新され,関数からリターンされるとその情報を破棄します。
ブレークポイントは行ごとに,その行にブレークポイントがあるかどうか判断して,ブレークポイントがあればその行で処理を止めます。ステップ実行は,ステップ実行を行った時のコンテキストを記録しておき,行ごとにその行で止まるべきか判断させます。たとえば,ステップアウトの場合は,ステップアウトを実行した場所より,上の階層になった場合に止まるようにコールスタックの状態を見て判断しています。
debug.getlocal, debug.setlocal
debug.getlocal
を使うと,その行から相対的に指定した階層のローカル変数を取得できます。debug.setlocal
を使うとその逆ができます。この2つの関数を使って,ローカル変数の一覧を取得し,チャンクの評価で変数の内容が変わったときに変更させています。同様にクロージャで,上位値の取得・変更をする場合は,debug.getupvalue
とdebug.setupvalue
を使います。
チャンクの評価
Lua5.2ではload
に文字列を渡すとそれをLuaのチャンクとして評価します。環境を指定しない場合は,グローバル環境_ENV
と同様の環境で評価されます。ローカル変数を反映させるには,自分で環境を作り,引数に渡します。
また,Lua5.1ではload
はファイルを読み込むことしかできないので,代わりにloadstring
を使います。loadstring
は環境を指定することができないので,debug.setfenv
を使って,事前に環境を設定する必要があります。
collectgarbage
collectgarbage
はdebug
ライブラリではありませんが,非常に強力です。引数に"stop"
を指定するとGCの動作を止めることができ,ゲームのコアなパートや重要な演出を表示しているときなど,GCを走らせたくないときに使えます。また,引数に"count"
を指定すると,現在Luaの処理系で使用しているメモリの使用量(キロバイト)が取得できます。LUPEでは,この機能を使って関数のメモリの使用量を計測する簡易プロファイラを実装しています。
まとめ
この記事では私が開発しているLUPEの紹介とそこで使われているLuaの強力なdebug
ライブラリの紹介を行いました。LUPEは現在開発中で,おそらくバグもいっぱいあると思います。しかしながら,ここまで機能を提供するピュアLuaで書かれたデバッガは他に無いかと思います。デバッガなので,開発の現場で使っても誰にも迷惑を書けることはありません。そのため,誰かの開発の役に立てればと思います。使ったよって報告がもらえるとテンションあがるので,お願いします。
まだ,公開はされていませんが,LUPEのWebフロントエンドを作っています。完成すれば,マウス使ってデバッグができるようになると思います。