这是一个VSCode的调试插件,用于调试skynet中的Lua程序,下面是详细的使用指南。

构建skynet

要想支持调试功能,你得使用这个skynet版本:

https://github.com/colinsusie/skynet

这个版本和官方的版本完全一致,并且会一直合并最新的修改;由于skynet极其精简的内核,所以实现这个调试器并没有修改框架的代码,只是增加了几个额外的模块:

  • cjson 用于和VSCode进行json格式的数据交换。
  • vscdebuglog.lua 用于替代skynet默认的logger服务,使日志能输出到VSCode的控制台。
  • vscdebugd.lua 一个专门和VSCode交互的lua服务。
  • vscdebug.lua 注入到skynet.lua的调试模块。

skynet的构建方法请看WIKI

安装扩展

在VSCode的Extensions面板中搜索Skynet Debugger,安装这个插件,该插件不支持Windows,如果你在Windows下工作,那么可以通过VSCode的Remote SSH打开远程服务器上的skynet工程,然后再安装Skynet Debugger,此时插件会安装在服务器上,这样就可以在Windows下编辑和调试服务器上的skynet工程。

插件的发布版只包含了在Debian GNU/Linux 8.8(jessie)-64bitmacOS 10.15.2(Catalina)下编译的可执行程序,在这两个系统中应该是可以运行起来的。其他平台则需要自己重新构建:

  • 克隆代码:git clone https://github.com/colinsusie/skynetda.git
  • 构建:cd skynetda; make linux
  • 完成之后在vscext/bin/linux中有skynetdacjson.so两个文件,需要将这两个文件拷贝到插件的安装目录去:
    • 如果是SSH远程服务器,插件目录应该在:~/.vscode-server/extensions/colinsusie.skynet-debugger-x.x.x/bin/linux/
    • 如果是Linux系统的本地插件,则应该在:~/.vscode/extensions/colinsusie.skynet-debugger-x.x.x/bin/linux/
    • 上面的x.x.x替换为具体的版本号

配置launch.json

插件安装完毕之后,打开skynet工程,在Run and Debug面板中创建一个launch.json文件,内容如下:

{
    "name": "skynet debugger",
    "type": "lua",
    "request": "launch",
    "program": "${workspaceFolder}",
    "config": "./examples/config_vsc"
},

program是skynet执行程序所在的目录

config是skynet所需的配置文件,这个路径是相对于skynet目录的,当然你也可以用绝对路径,比如:${workspaceFolder}/examples/config_vsc

配置skynet的config文件

要使skynet运行之后可以被调试,还需要修改一下config文件:

root = "./"
thread = 4
logger = "vscdebuglog"
logservice = "snlua"

logpath = "."
harbor = 0
start = "testvscdebug"  -- main script
bootstrap = "snlua bootstrap"   -- The service for bootstrap

luaservice = root.."service/?.lua;"..root.."test/?.lua;"..root.."examples/?.lua;"..root.."test/?/init.lua"
lualoader = root .. "lualib/loader.lua"
lua_path = root.."lualib/?.lua;"..root.."lualib/?/init.lua"
lua_cpath = root .. "luaclib/?.so"
snax = root.."examples/?.lua;"..root.."test/?.lua"
cpath = root.."cservice/?.so"

vscdbg_open = "$vscdbg_open"
vscdbg_bps = [=[$vscdbg_bps]=]
  • 修改loggerlogservice,将默认logger指定为vscdebuglog
  • 加上vscdbg_open = "$vscdbg_open"vscdbg_bps = [=[$vscdbg_bps]=],调试器通过$vscdbg_open告诉skynet是否要开启调试,另外$vscdbg_bps是初始的断点信息,最好如示例那样用[=[...]=]来包含。

这一份config一般用于开发期的调试用,发布的版本再用正式的config。

开始调试

到这里准备工作已经完毕,你可以在代码中设置断点,然后按F5开始调试,效果如下图所示:

f8adf5171e1180dde3436f80af46e1df.png

该插件只能调试外部写的服务,skynet/sevice目录中的服务不可调试。

vscdebug的功能

vscdebug实现了大多数常用的调试功能:

  • 它可以将skynet.error输出到DEBUG CONSOLE面板,点击日志还可跳转到相应的代码行;但是print, io.stdout就不行,调用这两个函数什么也不会输出。
  • 除了可以设置普通断点外,还支持以下几种断点:
    • 条件断点:当表达式为true时停下来。
    • Hit Count断点:命中一定次数后停下来。
    • 日志断点:命中时输出日志。
  • 当程序命中断点后停了下来,你就可以:
    • 查看调用堆栈。
    • 查看每一层栈帧的局部变量,自由变量。
    • 通过WATCH面板增加监控的表达式。
    • 可在DEBUG CONSOLE底部输入表达式,该表达式会在当前栈帧环境中执行,并得到结果输出。
  • 支持Step into, Step over, Step out, Continue等调试命令。

vscdebug怎么工作

编写skynet调试器的难点在于:skynet里面有很多个Lua虚拟机,并且这些虚拟机是在多个线程中运行的。要像原生调试器那样把整个程序冻住似乎有些难度,我最后决定像skynet的DebugConsole那样,让它在同一时刻只能调试Lua服务的一个协程,除了这个被调试的协程会停住,其他协程还是照常执行。所以断点命中后,看起来像是停下来了,其实它还在快速的处理消息。

它的实现是这样的:当一个协程的调试Hook回调时,如果命中断点,那么Hook函数会调用lua_yield停掉这个协程;接下来就可以对这个协程进行各种”观察”。此后执行单步调试,会使该协程执行一行后又被yield,如此重复,直到执行继续命令。

由于Lua服务的主协程不能yield,所以主协程不可以调试。但这个限制没什么大问题,因为skynet的主协程主要用于派发消息,具体的消息处理都在其他协程完成的。

在开发过程中,我发现了Lua的一个BUG,就是被Hook函数调用lua_yield的协程,它的CallInfo的savedpc会往前退一条指令,这就导致那一层的行数和局部变量不正确,修正方法是在ldebug.c

static int currentpc (CallInfo *ci) {
  lua_assert(isLua(ci));
  // 如果处于CIST_HOOKYIELD状态,应该加1。
  const Instruction *pc = (ci->callstatus & CIST_HOOKYIELD) ? ci->u.l.savedpc + 1 : ci->u.l.savedpc;
  return pcRel(pc, ci_func(ci)->p);
}

修改后问题解决了,这也是唯一修改过的底层代码。

Enjoy!!!

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐