淺析Lua調試器的實現

摘要

本文簡單介紹瞭如何實現一個Lua調試器,實現Lua調試器的目的僅僅是寄希望藉此熟悉Lua源代碼。所編寫的Lua調試器功能越強,表明你對Lua源碼越瞭解。

正文

先前用lua寫過一些應用,感覺Lua是一個很小巧的語言,Lua源代碼無疑是研究語言相關的首選。“Lua雖小,五臟俱全”!爲了研究Lua源代碼,就打算着手寫一個簡單的Lua調試器,發現其中還是有些收穫的,特記錄如下。

作爲一個調試器,應該支持一些最簡單而又常用的功能,比如:單步跟蹤、輸出調試信息、設置斷點等。要探索如何實現Lua調試器,還是帶着這些問題去找答案吧。本文使用的開發環境爲:win7,lua 5.1.4源代碼。

1.Lua虛擬機是如何暫停的?

Lua虛擬機和普通的CPU一樣,包含兩部分:數據存儲區和邏輯控制區。數據存儲區對應着CPU的寄存器、狀態等,在Lua中實際上就是lua_State。邏輯控制區對應着CPU的每條指令的具體實現。Lua虛擬機邏輯控制區的相關的源代碼位於lvm.c中。其中,執行Lua指令的函數爲luaV_execute。

爲了方便調試,函數luaV_execute在執行每條Lua指令之前,會去查找是否存在調試鉤子(hook):存在的話,去執行鉤子。然後,判斷Lua虛擬機的狀態是否爲暫停,若是的話就返回,而不執行當前Lua指令。若不存在調試鉤子,則正常執行Lua指令。

   1: if ((L->hookmask & (LUA_MASKLINE | LUA_MASKCOUNT)) &&
   2:     (--L->hookcount == 0 || L->hookmask & LUA_MASKLINE)) {
   3:   traceexec(L, pc); // 內部會執行相應的鉤子函數
   4:   if (L->status == LUA_YIELD) {  // 鉤子函數是否將狀態轉爲暫停?
   5:     L->savedpc = pc - 1;
   6:     return; // 此處離開函數luaV_execute,導致虛擬機暫停執行
   7:   }
   8:   base = L->base;
   9: }

由此想到一個辦法可以讓Lua虛擬機暫停:
首先,設置鉤子函數,可以使用函數lua_sethook來實現。通常Lua調試器要支持單步跟蹤,可以使用LUA_MASKLINE類型的鉤子。但是要注意的是,這個鉤子函數會在執行一條Lua指令之前觸發。
然後,鉤子函數中修改Lua虛擬機的狀態。可以使用Lua的C函數API lua_yield。該函數只是簡單的Lua虛擬機的狀態設置爲LUA_YIELD,這樣可以保證在執行指令之前退出。

2.Lua虛擬機是如何繼續執行的?

瞭解了Lua虛擬機是如何暫停之後,就很容易看到,可以採用如下步驟:首先,將Lua虛擬機的狀態設置爲0(正常狀態),然後執行函數luaV_execute即可。這兩步操作可以採用Lua的C函數lua_resume即可。

3.Lua調試器的其它功能該如何實現?

其它的一些功能,比如:獲取Lua虛擬機中的一些信息,這些還是比較容易實現的。因爲,一旦Lua虛擬機暫停後,可以通過查找lua_State中的信息來查詢,具體怎麼查詢,那就取決於你對lua源代碼的熟悉程度了。反正都在lua_State裏面,可以直接獲取的。

4.Lua調試器究竟該怎麼實現?

考慮到,調試器可能是命令行版本的,也可能是包含界面的調試器。可以考慮將調試器作爲一個庫來實現,然後這個庫提供了一些接口,方便和前臺銜接。一下就是我封裝的一些接口,僅供參考:

   1: ECode luad_init(const char * filename);
   2: ECode luad_command_step(int * pErr);
   3: ECode luad_command_go(int * pErr);
   4: ECode luad_command_bk(int line);
   5: ECode luad_command_bkinfo(int ** ppBklines, int * pNum);
   6: int luad_currentline();
   7: Boolean luad_is_script_ended();

這個庫加上前段的命令輸入控制,就很容易做出一個命令行版的Lua調試器了。同理,做界面版的也很容易。下面是我寫的Lua調試器命令行版運行截圖。

6]{NM2%OH9M~0JG5%%U[73D

發佈了86 篇原創文章 · 獲贊 10 · 訪問量 43萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章