简介:
LUA没有自带调试器,只提供了一套调试库,可以实现符合自己需要的调试器.晚上没事,改写了一下以前的一个GDB风格的LUA调试器,可嵌入到应用程序中,在需要的时候触发并调试,有需要的朋友可以参考下. 支持如下命令:
h 帮助信息
c 继续动行
s 单步运行(不跳过函数调用)
n 单步运行(跳过函数调用)
p var 打印变量值
b src:line 添加断点,注意src要写文件的绝对路径,例如 b script/main.lua:22
d num 删除断点
bl 列出所有断点
be num 启用一个断点
bd num 禁用一个断点
bt 打印调用栈
实现:
LUA支持用debug.sethook设置三种Hook:
一,每行代码执行时调用Hook函数
二,每个函数调用时执行Hook函数
三,每个函数返回时调用Hook函数
在Hook函数中,可以通过调用debug.getinfo得到当前LUA文件名,当前所执行代码的行号.所以,要实现针对行下断点,一个显尔易见的办法就是在每个HOOK中判断当前文件和当前行号是否是一个断点,是的话就中断下来(调用io.read()来等待输入).这样当然会比较慢,因为每行代码执行时都会去调用HOOK函数,但LUA一般是用来做逻辑而不是算术密集的操作,而且一般只是在开发期调试会使用hook,所以绝大部分情况下是足够用了.特殊情况下有效率要求时,可以使用下文介绍的另一种办法.
但在实际开发中碰到了个问题:如何实现n命令(单步运行,跳过函数调用),LUA的HOOK是每行代码都会触发,可以取得当前函数,所以单步运行时很容易知道是不是进入了一个新函数,但是如何在进入这个函数时不中断,在调用完成时才中断?最直观的想法是在n命令碰到函数时,自动在函数调用的下一行加入断点并运行.但是实际情况要复杂得多,比如函数调用下一行是空行,函数调用当前行有return,比如:
if ( foo() ) return end
这样就要写很多代码分析的代码来保证正确性,逻辑就写复杂了,放弃这种做法.
另一种想法是下函数执行hook和函数返回hook,执行hook触发时增量某个变量,返回hook扫许时减量某个变量,这样变量为0时就是返回到调用函数了(调用栈平衡),但是程序逻辑也变得复杂了,放弃这种做法.
最直观的办法是检查调用栈深度,因为被调用函数返回时,调用栈深度总是不会变的.但是LUA debug库没有检查调用栈深度的函数,还好LUA是开源的,动手加一个即可.最后的代码见下面,如有更简单的办法请告之:
在lua(5.1.4)源代码文件 ldblib.c中添加返回调用栈深度的函数:
static int db_traceback_count (lua_State *L) {
lua_Debug ar;
int index = 1;
while (lua_getstack(L, index, &ar))
index++;
lua_pushnumber( L, index - 1 );
return 1;
}
在static const luaL_Reg dblib[] 数组中添加一行函数注册:
{"traceback_count", db_traceback_count},
重新编译LUA.
为了像GDB一样在单步调试时能显示当前源代码,在C/C++层注册一个读取指定文件指定行的函数:
int get_file_line( lua_State *L )
{
const char *file = luaL_checkstring( L, 1 );
int line = lua_tonumber( L, 2 );
if ( line < 1 ) line = 1;
int n = 1;
char src[2046];
FILE *f = fopen( file, "r" );
if ( f )
{
while ( fgets( src, 2046, f ) )
{
if ( n == line )
{