您的位置:首页 > 编程语言 > Lua

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,大功告成!

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: