Lua中如何实现类似gdb的断点调试--02通用变量打印
2022-03-08 17:07
1321 查看
在前一篇01最小实现中,我们实现了Lua断点调试的的一个最小实现。我们编写了一个模块,提供了两个基本的接口:设置断点和删除断点。
虽然我们已经支持在断点进行变量的打印,但是需要自己指定层数以及变量索引,使用起来不是很方便。要进行upvalue打印的话,操作会更加麻烦。为了提升调试的方便性,我们决定封装一个通用的变量打印函数,可以通过变量名查找到对应变量的值进行打印。支持局部变量、upvalue以及全局的
_ENV中的变量。
本文代码已开源至Github,欢迎watch/star😘。
本博客已迁移至CatBro's Blog,那里是我自己搭建的个人博客,欢迎关注。
因为函数比较长,我们分几部分进行说明,该函数有三个参数:
name为要查找的变量的名字,
level指示在哪个层级的函数中查找,
isenv是个标记我们稍后再提。层级的默认值为1, 注意将层级加上1,是为了将层次修正为包含
_getvavalue函数自己。
然后遍历局部变量表,比较变量名字是不是等于
name,如果匹配的话记录其值,并且标记一下我们已经找到。注意我们找到之后并没有立马跳出循环,因为可能具有多个同名的局部变量,我们应该获取索引最大的那个。
循环结束之后,如果已经在局部变量中找到了
name,就返回
"local"和变量的值。
local function _getvarvalue (name, level, isenv) local value local found = false -- 加1将层次纠正为包括_getvarvalue自己 level = (level or 1) + 1 -- 尝试局部变量 for i = 1, math.huge do local n, v = debug.getlocal(level, i) if not n then break end if n == name then value = v found = true -- 这里不跳出,获取具有最大索引的那个局部变量 end end if found then return "local", value end -- 省略 end
上值中查找
如果在局部变量中没有找到,我们再尝试到upvalue中进行查找。首先通过
getbug.getinfo获取到第level层的函数,然后遍历其上值,如果找到匹配的变量就返回
"upvalue"和变量值。
local function _getvarvalue (name, level, isenv) -- 省略 -- 尝试非局部变量 local func = debug.getinfo(level, "f").func for i = 1, math.huge do local n, v = debug.getupvalue(func, i) if not n then break end if n == name then return "upvalue", v end end -- 省略 end
_ENV表中查找
如果在普通的上值中还是没有找到,我们就去
_ENV表中查找。
isenv标志表示当前
name是否就是
"_ENV",是用来防止无限循环调用的,第一次调用的时候肯定不是。然后将
"_ENV"作为
name递归调用
_getvarvalue。因为多了一次函数调用,第二次调用的时候level又会自动加1。接下来还是先后在局部变量和上值中查找,找到了就返回类型和变量值。没有找到的话,返回
"noenv"。
然后返回到外层的
_getvarvalue,判断第二个返回值是否为真,如果是说明找到了
_ENV表,就从
_ENV表中获取名为name的值,否则直接返回
"noenv"。
local function _getvarvalue (name, level, isenv) -- 省略 if isenv then return "noenv" end -- 避免无限循环 -- 没找到,从环境中获取 local _, env = _getvarvalue("_ENV", level, true) if env then return "global", env[name] else return "noenv" end end
包装函数
_getvarvalue函数已经定义好了,我们再定义一个包装函数
printvarvalue。如果第二个返回值为真,表示找到了变量,就打印变量类型及结果,否则提示未找到。
{% label warning@注意到 %}我们这里将
level层次数加了4,目的是跟
_getvarvalue函数中类似,也是为了修正层次数以包含
printvarvalue函数自身以及其上层的
debug mainchunk、
debug.debug以及钩子函数。这样当
level参数为1时就表示断点所在的函数。同样地,如果level不指定默认为1,即断点所在函数。2表示断点所在函数上一层,以此类推。当然如果你有特殊需求,你也可以指定层次为0,查看hook函数的情况。
-- 包装 _getvarvalue, 打印结果 local function printvarvalue (name, level) -- level默认值1 -- 加4,将层次纠正为包含 printvarvalue, debug mainchunk, debug.debug和hook level = (level or 1) + 4 local where, value = _getvarvalue(name, level) if value then print(where, value) else print(name, "not found") end end
最后将
printvarvalue函数作为模块的函数输出。
return { setbreakpoint = setbreakpoint, removebreakpoint = removebreakpoint, printvarvalue = printvarvalue, }
测试脚本
OK,调试库我们已经改好了,接下来将我们之前的测试脚本
test.lua稍做修改。在开头添加如下一行:
pv = luadebug.printvarvalue
为了方便在断点内部调用,我们将其写到全局变量里了。然后调整下断点的行号,换一下断点删除的顺序,其他内容保持不变。
local luadebug = require "luadebug" local setbp = luadebug.setbreakpoint local rmbp = luadebug.removebreakpoint pv = luadebug.printvarvalue -- 增加这一行 g = 1 local u = 2 local function foo (n) local a = 3 a = a + 1 u = u + 1 g = g + 1 end local function bar (n) n = n + 1 end local id1 = setbp(foo, 12) -- 行号调整 local id2 = setbp(bar, 17) -- 行号调整 foo(10) bar(10) rmbp(id2) -- 先删除断点2 foo(20) bar(20) rmbp(id1) -- 再删除断点1 foo(30) bar(30)
测试验证
接下来,让我们来测试一把。可以看到无论是局部变量、upvalue、还是全局
_ENV表中的变量,都可以很方便地获取值。
$ lua test.lua (local)foo test.lua:12 lua_debug> pv("a") local 4 lua_debug> pv("u") upvalue 2 lua_debug> pv("g") global 1 lua_debug> pv("x") x not found lua_debug>
第二次停到foo函数断点处,
u和
g都已经加1。
lua_debug> cont (local)bar test.lua:17 lua_debug> cont (local)foo test.lua:12 lua_debug> pv("a") local 4 lua_debug> pv("u") upvalue 3 lua_debug> pv("g") global 2 lua_debug>
我们尝试显式指定层数,结果一样
lua_debug> pv("a", 1) local 4 lua_debug> pv("u", 1) upvalue 3 lua_debug> pv("g", 1) global 2 lua_debug>
我们再尝试打印上一层的变量(即main chunk),结果都符合预期。变量a是foo里的局部变量应该找不到,变量u在main chunk中是局部变量,全局变量g则没啥区别。
lua_debug> pv("a", 2) a not found lua_debug> pv("u", 2) local 3 lua_debug> pv("g", 2) global 2
OK,大功告成!
相关文章推荐
- gdb调试core时打印出当时变量的内容------有时可以主动制造core来看变量值
- 如何打印一个类的属性(例如textview里的控件等,基于oc里没有绝对的私有变量这一规则实现)
- 如何使用gdb打印Eigen中的变量
- 如何在vs (visual studio)调试环境下查看lua的调用栈、变量信息
- Solidity调试 - 实现变量打印
- swift OC混编工程,xcode断点调试,控制台左侧只有变量名称不显示值,右侧输入po命令,打印除一堆提示
- Android中app调试:gdb如何在动态链接库中设断点
- 利用nginx+lua实现通用的请求输入输出日志打印
- 关于断点调试输出信息出不来,用po打印变量也没有值的问题
- 用gdb调试C++程序时打印变量的值
- 用gdb调试C++程序时打印变量的值
- 用GDB动态打印快速实现嵌入式系统的调试输出
- visual c++6.0中如何使用观察变量呵断点调试
- gdb调试 打印输出长变量的值到外部文本里
- Lua 中如何实现类似C语言中 __FILE__, __LINE__, __FUNC__
- 如何在vs (visual studio)调试环境下查看lua的调用栈、变量信息
- Go语言gdb调试打印全局变量
- vc++如何实现远程调试
- 利用XML实现通用WEB报表打印(参考)
- vc++如何实现远程调试