Edited at
LuaDay 17

LuaでLuaデバッガのLUPEを作りました #lua

More than 3 years have passed since last update.


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
ウォッチ式の表示

上記以外

チャンクの評価

それでは,以下のコードをデバッガで追ってみましょう。


sample.lua

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.getupvaluedebug.setupvalueを使います。


チャンクの評価

Lua5.2ではloadに文字列を渡すとそれをLuaのチャンクとして評価します。環境を指定しない場合は,グローバル環境_ENVと同様の環境で評価されます。ローカル変数を反映させるには,自分で環境を作り,引数に渡します。

また,Lua5.1ではloadはファイルを読み込むことしかできないので,代わりにloadstringを使います。loadstringは環境を指定することができないので,debug.setfenvを使って,事前に環境を設定する必要があります。


collectgarbage

collectgarbagedebugライブラリではありませんが,非常に強力です。引数に"stop"を指定するとGCの動作を止めることができ,ゲームのコアなパートや重要な演出を表示しているときなど,GCを走らせたくないときに使えます。また,引数に"count"を指定すると,現在Luaの処理系で使用しているメモリの使用量(キロバイト)が取得できます。LUPEでは,この機能を使って関数のメモリの使用量を計測する簡易プロファイラを実装しています。


まとめ

この記事では私が開発しているLUPEの紹介とそこで使われているLuaの強力なdebugライブラリの紹介を行いました。LUPEは現在開発中で,おそらくバグもいっぱいあると思います。しかしながら,ここまで機能を提供するピュアLuaで書かれたデバッガは他に無いかと思います。デバッガなので,開発の現場で使っても誰にも迷惑を書けることはありません。そのため,誰かの開発の役に立てればと思います。使ったよって報告がもらえるとテンションあがるので,お願いします。

まだ,公開はされていませんが,LUPEのWebフロントエンドを作っています。完成すれば,マウス使ってデバッグができるようになると思います。