在前面两篇01最小实现及02通用变量打印中,我们已经实现了设置断点、删除断点及通用变量打印接口。
本篇将继续新增两个辅助的调试接口:调用栈回溯打印接口、通用变量设置接口。前者打印调用栈的回溯信息,后者可以方便地修改变量的值,支持局部变量、upvalue以及全局的_ENV
中的变量。
本文代码已开源至Github,欢迎watch/star😘。
调用栈打印函数
我们首先来实现调用栈回溯打印接口printtraceback()
,这个接口比较简单,只是简单地包装了一下debug.traceback()
,对层级进行了一个修正,就不多介绍了。
1 | -- 打印调用栈的一个回溯 |
通用变量值修改函数
接着来实现通用的变量值修改函数_setvarvalue()
,这个函数的结构跟_getvarvalue()
类似,依次在局部变量、上值和_ENV
表中查找,只不过找到之后会修改相应的值。函数有三个参数,name
为要修改的变量的名字,value
是修改的目标值,level
指示在哪个层级的函数中查找,我们同样分成几部分来看。
1 | local function _setvarvalue (name, value, level) |
局部变量中查找
同样地,先处理层级,层级的默认值为1, 将层级加上1是为了将层次修正为包含_setvavalue
函数自己。
然后遍历局部变量表,查找是否有名字为name
的变量,如果找到的话记录其索引。注意我们找到之后并没有立马跳出循环,因为可能具有多个同名的局部变量,我们应该获取索引最大的那个。
循环结束之后,如果已经在局部变量中找到了name
,就修改该变量的值为value
,然后返回"local"
,指示修改的是局部变量。
1 | local function _setvarvalue (name, value, level) |
上值中查找
如果在局部变量中没有找到,我们再尝试到upvalue中进行查找。首先通过getbug.getinfo
获取到第level层的函数,然后遍历其上值,如果找到匹配的变量就修改其值为value
,然后返回"upvalue"
以指示修改的是上值。
1 | local function _setvarvalue (name, value, level) |
_ENV表中查找
如果在普通的上值中还是没有找到,我们就去_ENV
表中查找。首先调用_getvarvalue
获取到_ENV
表,注意这里的isenv
标志为true
。如果如果找到了_ENV
表且表中存在名为name
的变量,就修改其值为value
,然后返回"global"
以指示是修改的_ENV
表中的值。如果没有_ENV
表或表中不存在要找的变量,就返回nil
。
1 | local function _setvarvalue (name, value, level) |
包装函数
接下来我们同样再定义一个包装函数,对层次数level
进行修正以包含setvarvalue
函数自身以及其上层的debug mainchunk
、debug.debug
以及钩子函数。
然后对返回值进行检查,如果返回值为真,说明修改变量值成功,就打印变量类型,否则提示未找到。
1 | local function setvarvalue (name, value, level) |
接口定义好了,让我们把这两个接口也添加到返回到表中。
1 | return { |
测试脚本
接下来编写一个测试脚本test.lua
以对我们新添加的接口进行测试。脚本很简单,就不多做解释了。
1 | local ldb = require "luadebug" |
测试验证
我们运行测试脚本,首先调用堆栈打印函数,默认是打印从断点所在函数开始的堆栈。
1 | $ lua test.lua |
我们显式指定层数试一下。
1 | lua_debug> ptb(2) |
没有问题,层数为2的时候少打印了一层,为0的时候则多打了一层。
我们再来测试下变量值修改函数,先看来变量原来的值
1 | lua_debug> pv("a") |
然后修改变量的值,我们把这三个变量值都改成了6
1 | lua_debug> sv("a", 6) |
然后再打印下值检查下结果,可以看到都修改成功了。
1 | lua_debug> pv("a") |
我们再试试显式指定层级为2,将变量值再改为8
1 | lua_debug> sv("a", 8, 2) |
变量a因为是函数foo的局部变量,所以外层看不到。变量u在main chunk中是属于局部变量,而变量g则还是全局变量。修改之后变量a的结果没有变,其他两个都改成了8。
Well done!