最近看起了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。