« X Window 的 Resize 处理 | 返回首页 | lua 中判断字符串前缀 »

lua 调试器制作注意

前两年写过一个 lua 的调试器,blog 上有截图

不过调试器设计的关键不在于界面,在于调试协议。前两年的那个是设计的不完整的。

最近同事强烈要求引擎提供一个强力的调试工具,虽然我个人不太依赖调试去写代码。甚至认为,经过反复调试才正确工作的代码不是好代码。不过周末还是花了点时间重新制作了一个 lua 调试器。

中间发现一些问题,非常让人吐血。列在这里,做个记录。

一开始我比较担心 lua 的 debug hook 会降低运行性能。所以考虑的优化比较多。

都说提前优化是万恶之源,此话绝对不假。可挡不住的诱惑是,制作一个几乎不影响运行性能的调试器,更为实用。而且这个东西我构思了好几年,优化策略也思考再三,不算提前优化吧。

基本优化策略是把被调试程序分成三种调试状态:

  1. 无须 hook 的状态。这个状态下,没有设置断点,lua 程序可以全速去跑。只要定期查询一下,调试器有没有指令输入即可。( 和 gdb 一样,这个调试器支持且暂时仅支持远程调试)这个轮询,可以放在程序主循环内,定期跑一下即可。几乎不会影响正常的程序运行。这样,我可以随时 attach 到正在工作的 lua 进程上。

  2. 高密度的 hook 状态,使用 lua debug hook 的 line 模式。每条 lua 指令都被 hook ,用来检测断点的工作。

  3. 低密度的 hook 状态。仅使用 lua debug hook 的 call/return 模式。这个模式下,仅仅返回进入和返回被 hook ,可以做一些粗略的判断。

实现的时候发现几个问题。

我原来的计划中,使用 call/return 的 hook 消息,监视 lua 运行所在的源文件/ 函数。 一旦发现没有断点,则可以使用第三模式运行,提高被监控状态的运行效率。

当然,如果所有断点都被 disable ,则切换到第一状态。

事实上,简单的在 call/return 的 hook 内获取上级函数的 source 是不够的。因为,lua 的 call hook 发生的时候,已经进入被调用函数;而 lua 的 return hook 发生,尚还停留在调用函数的最后一行。也就是说,可能没有机会回到被调用者。

比如在 a.lua 中写上两个函数调用 :

foo1()

foo2()

而把 foo1 和 foo2 定义在 b.lua 中。如果仅仅 hook call/return 。会发现 foo1() 和 foo2() 调用期间,从 hook 中,没有回到 a.lua 。假设在 foo2() 上设置断点,就没有机会断下来。

想来想去,补救的方法是:当发生 return 或 tail return 时,多看一级堆栈,获得调用者的 source 名。不过这样还是有点问题,有可能调用者是从 C 或是一段运行期产生的代码中过来,依然回溯不到正确的位置。正确的做法是一直回溯到可识别的源代码文件名。( @ 开头的字符串)

最后再查表判定是否需要切换 hook 的 mask 状态。

另一点是关于 lua 的 tail return 的,return 和 tail return 是分开的事件,在实现 step over 的功能时务必小心。

关于无效代码行的判定没有想到特别好的方案。即设置断点时,如果设置在无效行上,应该向上移动到有效位置。虽然 lua 的 debug 模块提供了一些相关的支持,但是比较有限。靠监控运行的当前函数来反复匹配合适的断点位置,代码写起来会过于繁琐。

在调试器中查看变量的值,最好注意一下潜在的副作用。主要是 metatable 造成的。用 rawget 去取 table 里的数据更靠谱一些,需要小心的是是 tostring 的 meta 方法调用。

lua 的高阶用法中,往往会由一段代码生成新的代码运行。让调试器识别这种情况,并给予支持,会带来许多方便。(主要是格式化源码,并对 unix 和 dos 的回车做兼容比较麻烦)


周六一天本来把调试器已经写完,后来发现一些隐藏的 bug 。越看代码越不顺眼,然后周日推翻重写了一次。

新的版本主要是建立起一个调试状态机。把调试器的各种状态严格区分。比如运行态和阻塞交互状态。以及大状态下的小状态划分。

因为后面会做一个图形交互前端,对调试指令的协议定义要求比较严格。文档也仔细研究过,参考了 gdb 的协议。


做完的感觉就是累。没想到后面有没完没了的小需求加进来。幸亏中途一咬牙重写了。否则肯定在周末是完成不了的。不算 socket 通讯部分(以前自己实现的一个模块),暂时有 1000 行 lua 代码吧,比第一个版本简洁很多,但比我预期代码规模大一些。也不能算太好看。


btw,我在选择调试器默认端口时,选了个 3563 。16 进制为 0xdeb 。 昨天 Sean 同学告诉我,这个是个知名端口: 乃 Wacom C 的调试器端口。吼吼,真是英雄所见略同。

Comments

不知道能不能附加调试

请教大神, go + lua 怎么调试呢

我很尊重云风,但是你对调试程序的看法也忒不对我胃口了,我们都常说“程序都是调试出来的”这句话

基于VS2012/2013的专业Lua编辑调试工具https://babelua.codeplex.com/

好像是 Watcom c

LuaStudio7.23 破解版

http://www.vdisk.cn/down/index/12815647

推荐一个lua调试器
http://www.crsky.com/soft/26744.html

我是这样做的,自己实现一些函数,作为luaL_dofile, luaL_dostring的替代品(并定义一些宏作为替换,发布时可以快速改回到原来的luaL_dofile, luaL_dostring)。这些函数会连接到调试逻辑,调试逻辑又连接到调试界面。
一切都在同一个程序里面做,所以也不叫调试器了,仅仅是一个调试模块。没有远程,没有socket,也没考虑优化。
调用堆栈,局部变量、全局变量、upvalues,该有的东西都基本有了。若要说“特色”的话,就是对于thread也可以像函数一样的跳入、跳出,如履平地。

新的基于Socket的远程Lua调试器写好了,欢迎大家使用并提出宝贵意见。请参考
http://sites.google.com/site/louirobert/Home/lua/remote-debugger-based-on-socket

PS:楼下的连接失效了,我重新调整过页面,新的地址是:
http://sites.google.com/site/louirobert/Home/lua/Console-Based-Local-Debugger

我最近也写了一个Lua调试器,基于命令行的本地调试器,纯C的,以Lua包的形式发布(目前只在Windows上,不过编译一个Linux版本的也不难).我准备把它改进成为基于GUI的、具有远程调试功能的、多平台的Lua调试器。详细请看:

https://sites.google.com/site/louirobert/instruction

欢迎大家试用并提出意见:)

我最近也写了一个Lua调试器,基于命令行的本地调试器,纯C的,以Lua包的形式发布(目前只在Windows上,不过编译一个Linux版本的也不难).我准备把它改进成为基于GUI的、具有远程调试功能的、多平台的Lua调试器。详细请看:

https://sites.google.com/site/louirobert/instruction

欢迎大家试用并提出意见:)

云风大哥:
我对你这段话很感兴趣“虽然我个人不太依赖调试去写代码。甚至认为,经过反复调试才正确工作的代码不是好代码。”
那么,你是如何做到代码正确的呢?是通过测试来实现的么?

在没有断点时,在要调试的程序中,主循环里加几行 checkpoint 的调用。不必用 hook 逐次回调检查。

最近也在搞这个东西,主要是没办法解决效率问题,Decoda也用了下,那速度实在是太慢,我们的脚本运行起来后3,4秒就会有1W以上的CR事件发生,用Decoda根本没办法调试。
不知道风云是上面的那几种解决方法是怎么弄的,尤其是在没有HOOK的时候是如何实现定期检查是否有调试命令?是在Lua中嵌入别的东西实现多线程吗?

我也写了一个调试器,不过效率比较低。因为是用的密集的line模式。调试内核可以随时和host挂接,调试端有Consol和LuaStudio改的两个版本。一直以来也没有时间做优化,看了你的优化策略突然感到非常的想将那个调试器按你的思路再做一次优化。等这个周末我再去试试!
将我的调试器放出来,希望您能给出一些建议。也非常高兴看到大家一起来讨论!
http://www.brsbox.com/filebox/down/fc/3f962c4e9001b6d911687c0f9cd51e5c

PS:云风大大,我已经崇拜你很久了。哈哈

123

确实不错,最好能开源咯

decoda我的项目就在用,总体上来说很好,不过有时候在调试一些特定的代码,会遇到比较致命的bug,很难形容,反正不是让你的host卡住,就是没法设断点.你在一段时间内就只能回到print调试阶段.
由于这个原因,我也在考虑什么时候也自己写个debugger了.

7楼推荐的decoda还真强,不知道是咋弄的.

我现在在用一个日本人写的开源的debugger
http://luaforge.net/projects/lldebug/

也非常好用

不知道云风大大用过decoda没?调试Lua的话我觉得没有比decoda这个工具更好的了,用起来真的是很舒服。我的blog里面有我最新破解的版本。
http://sailtsao.blogbus.com/

开源...但是,你们是项目需要,开源了可能会带来side effect的感觉。作为高层...哎,看来是看不到开源版了

好东西,希望云风能够开源

0xdeb,很有趣

既然是调试,为什么这么注重效率?发行版难道不禁用 debug 库?
最近也在实现一个,已有雏形:)

强烈要求开源,嘿嘿

Post a comment

非这个主题相关的留言请到:留言本