Lua断点调试 - 类似gdb的调试体验

发布时间 2023-10-05 16:55:43作者: Notify-ctrl

平时在做一个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(),但在出错后自动进入调试。