lua在调试的时候需要用到调试器,那么unity 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.  // 钩子函数是否将状态转为暂停?
  6.      L->savedpc = pc - 1;
  7.     return; // 此处离开函数luaV_execute,导致虚拟机暂停执行 
  8.  }
  9.    base = L->base;
  10.  }

由此想到一个办法可以让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调试器命令行版运行截图。

unity lua 调试器