最近看起了lua的debug库,想着可以简单弄个跟gdb这样的命令行调试工具,于是花了几天时间,搞了个相当简单的调试工具,实现了简单的打印和断点和下一步的功能(修改变量值的实现方式其实跟打印的操作并没有很大区别,所以就懒得弄啦),虽然代码很粗糙,使用和体验上也相当奇葩,也勉强把,毕竟也达到了学学习,动动脑子的目的,这里分享一下,有兴趣的大佬可以在github上整下来指点指点咯https://github.com/xlandxm/DDebugger。
这工具主要分为两部分,一个是用lua实现的处理实际的调试操作,一个是用C++整的控制台用于接收命令和显示输出。
这里就主要看下lua部分咯,目前这一块主要实现的功能有:断点、打印local和upvalue值。
1、断点
首先看下怎么把程序中断下来进入交互调试把。本来是准备用debug.debug()这个东西来中断程序执行进行交互的,可是想一下这个的输入输出都是标准输入输出,而且继续执行需要输入cont,这多麻烦哦。于是看了一下debug.debug()的实现,实现如下:
static int db_debug (lua_State *L) {
for (;;) {
char buffer[250];
luai_writestringerror("%s", "lua_debug> ");
if (fgets(buffer, sizeof(buffer), stdin) == 0 ||
strcmp(buffer, "cont\n") == 0)
return 0;
if (luaL_loadbuffer(L, buffer, strlen(buffer), "=(debug command)") ||
lua_pcall(L, 0, 0, 0))
luai_writestringerror("%s\n", lua_tostring(L, -1));
lua_settop(L, 0); /* remove eventual returns */
}
}
其实也就是一个进入一个循环,然后等待输入然后就各种各样什么的。这就没啥好想的了,原样整一个完事了
while true do
if not self.isBreak then
self:Print("[DDebuger] QuitDebug!!")
break
end
local data, e = self.conn:receive() --接收
if data and e ~= "closed" then
self:HandlerMessage(data)
else
self.conn:close()
self.conn = nil
break
end
end
一旦触发断点就进入一个循环,等待接受命令来执行特定的操作,如果连接断开就直接退出循环。
接着看下判断断点的出发。这个当时是有两个方案实现,一个是直接在需要断点的地方调用一个函数进入调试,另一个方案当然就是通过debug.sethook()来实现了。想了一下,为了能把操作统一到一个地方控制,还是选择了第二种方案。具体的做法是记录下断点的文件路径和行号,在每次进入下一行的时候判断是否需要断下来,就这么简单,具体实现如下:
local hookFunc = function ()
-- 获取当前的调试信息
local info = debug.getinfo(2, "nlS")
if not info then
return
end
-- 过滤该文件的hook
if self:CheckSelfFunc(info) then
return
end
-- 判断是否击中断点
if not self.isSingleStep and not self:CheckHitBreakPoint(info) then
return
end
-- 进入调试模式
self:EnterDebug(info)
end
debug.sethook(hookFunc, "l")
-- 判断是否击中断点
function DDebuger:CheckHitBreakPoint(info)
if not info then
return
end
if not self.bpList then
return
end
local _, _, short_src = string.find(info.short_src, [[\(%a+).lua]])
if not short_src then
return
end
if not self.bpList[short_src] then
return
end
if not self.bpList[short_src][tostring(info.currentline)] then
return
end
return true
end
当然具体还有一些判断是否是当前文件,是否是执行下一步这样的判断检测,不过主要的东西也就这样咯。
实现了这两部分功能的代码就假假的打个断点停下来这样啦,其他的功能也就一步步添加了,这里主要弄了个打印local变量和upvalue的功能。这一部分的实现主要还是参考了云风大佬的做法https://blog.codingnow.com/2006/11/lua_breakpoint.html。
大佬就是大佬,通过进入调试状态的时候遍历一下将两种变量存到一个表里面去,后续访问就变得相当简单了。具体的代码如下:
function DDebuger:InitLocalTableLua(level)
self.localTabel = {}
local n = 1
while true do
local key, value = debug.getlocal(level, n)
if not key then
break
end
if value == nil then
value = "nil"
end
self.localTabel[key] = value
n = n + 1
end
end
-- 初始化upvalue变量
function DDebuger:InitUpvalueTableLua(level)
self.upvalueTable = {}
local info = debug.getinfo(level, "nf")
local func = info.func
local n = 1
while true do
local key, value = debug.getupvalue(func, n)
if not key then
break
end
if value == nil then
value = "nil"
end
self.upvalueTable[key] = value
n = n + 1
end
end
到这里打印变量就不是啥问题啦,相应的要实现改变变量的操作其实核心也就是封装一下的debug.setupvalue什么的这些接口咯,这就不献丑了。
鉴于这博客应该没啥人看,就到这里把,假如有哪位大佬有意思进一步了解什么的就直接github下代码或者留言怎么的咯。
参考链接:https://blog.codingnow.com/2006/11/lua_breakpoint.html。