平时在做一个C++/Lua的项目,C++代码可以用gdb调试,但是Lua代码的调试却一直是个困扰人的难题。根据网上搜索的结果,无外乎都是用vscode插件调试,或者用socket之类的设施进行远程调试,个人都觉得太麻烦了,最好有个类似gdb那种直接在命令行中进行调试。
不过经过我在网上的搜索,终于还是找到了满足需求的调试器——debugger.lua。(项目地址 https://github.com/slembcke/debugger.lua ,下面简称dbg)
dbg的一大优点就是无需安装依赖、无需折腾各种socket,直接把lua文件扔进项目里面即可使用。调试器本身只有一个文件,并且都是只使用lua原生的debug库、io、os之类的设施实现的,支持Lua 5.x和LuaJIT。顺便一提这个是MIT协议开源的调试器,不必担心自己的项目被协议强制开源化。
虽然仓库的README里面已经写了详细的解说,但这里还是稍微记载一下自己的使用历程好了。
基本使用 - 断点与调试
[notify@npc dbg]$ ls
debugger.lua test.lua
如图,test.lua用来进行调试功能的测试,而debugger.lua则是调试器本身。test.lua的内容如下:
local dbg = require 'debugger'
local main = function()
local a = 123
local b = 456
local c = "Hello, world!"
dbg() -- 这里就是下断点了
print(c)
end
main()
像这样先把调试器require进来,然后使用dbg()
就能实现下断点了。
使用lua test.lua
去执行test.lua,此时代码执行到main函数的dbg()
处了,这就是触发了断点(debugger.lua不支持像各种IDE那样点击行号下断点,但其实这种操作方法也挺不错的)
[notify@npc dbg]$ lua test.lua
debugger.lua: Loaded for Lua 5.4
break via dbg() => test.lua:8 in local 'main'
debugger.lua>
这样就进入了调试界面。输入"h"命令即可查看所有的操作:
debugger.lua> h
<return> => re-run last command
c(ontinue) => continue execution
s(tep) => step forward by one line (into functions)
n(ext) => step forward by one line (skipping over functions)
f(inish) => step forward until exiting the current function
u(p) => move up the stack by one frame
d(own) => move down the stack by one frame
w(here) [line count] => print source code around the current line
e(val) [statement] => execute the statement
p(rint) [expression] => execute the expression and print the result
t(race) => print the stack trace
l(ocals) => print the function arguments, locals and upvalues.
h(elp) => print this message
q(uit) => halt execution
和gdb非常类似:c=继续执行,s=单步步入函数,n=单步下一行,p则可打印出某些值的信息。除此之外,help也给出了其他相当不错的命令,比如l可以输出当前所有的局部变量以及函数的参数、上值:
debugger.lua> l
a => 123
b => 456
c => "Hello, world!"
w命令可以查看当前代码(类似gdb的l命令):
debugger.lua> w
3 local main = function()
4 local a = 123
5 local b = 456
6 local c = "Hello, world!"
7 dbg() -- 这里就是下断点了
8 => print(c)
9 end
10
11 main()
输入n命令执行下一行,此时打印出Hello, world,函数也即将返回。
debugger.lua> n
Hello, world!
test.lua:9 in local 'main'
此外,打印函数栈、print、eval等操作对于熟悉gdb调试的人应该都不难理解。只能说debugger.lua的体验确实相当不错呀。
活用API
用dbg()
进行简单下断点已经能满足绝大多数情况的需求了,但是debugger.lua还提供了更多实用功能,除了dbg()
之外我们还能用更多好用的函数解决其他方面的问题。
dbg.writeln(fmt, ...)
: 基本上就是print(string.format(fmt .. "\n", ...))
dbg.pp(obj)
: 输出某个object(通常是表)的看起来更加直观的形式。比如他会输出{"a" = 1, "b" = 3}
而不是table: 0x55fc28e72ef0
dbg.auto_where = int_or_false
: 这个值一般是在初始化debugger.lua的时候设置的。设为true的话,每中断一次就自动执行w命令让你看清中断在哪一行了。dbg.error(error, [level])
: 类似自带的error()
,但是输出错误信息后自动进入调试。dbg.assert(exp, [message])
: 同上。dbg.call(f, ...)
: 类似pcall()
,但在出错后自动进入调试。