在前一篇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 mainchunkdebug.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函数断点处,ug都已经加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,大功告成!

Lua中如何实现类似gdb的断点调试--02通用变量打印的更多相关文章

  1. Lua中如何实现类似gdb的断点调试--03通用变量修改及调用栈回溯

    在前面两篇01最小实现及02通用变量打印中,我们已经实现了设置断点.删除断点及通用变量打印接口. 本篇将继续新增两个辅助的调试接口:调用栈回溯打印接口.通用变量设置接口.前者打印调用栈的回溯信息,后者 ...

  2. Lua中如何实现类似gdb的断点调试--01最小实现

    说到Lua代码调试,最常用的方法应该就是加一堆print进行打印.print大法虽好,但其缺点也是显而易见的.比如效率低下,需要修改原有函数内部代码,在每个需要的地方添加print语句,运行一次只能获 ...

  3. Lua中如何实现类似gdb的断点调试--04优化钩子事件处理

    在第一篇的01最小实现中,我们实现了一个断点调试的最小实现,在设置钩子函数时只加了line事件,显然这会对性能有很大的影响.而后来两篇02通用变量打印和03通用变量修改及调用栈回溯则是提供了一些辅助的 ...

  4. Lua中如何实现类似gdb的断点调试—09支持动态添加和删除断点

    前面已经支持了几种不同的方式添加断点,但是必须事先在代码中添加断点,在使用上不是那么灵活方便.本文将支持动态增删断点,只需要开一开始引入调试库即可,后续可以在调试过程中动态的添加和删除断点.事不宜迟, ...

  5. Lua中如何实现类似gdb的断点调试—07支持通过函数名称添加断点

    我们之前已经支持了通过函数来添加断点,并且已经支持了行号的检查和自动修正.但是通过函数来添加断点有一些限制,如果在当前的位置无法访问目标函数,那我们就无法对其添加断点. 于是,本篇我们将扩展断点设置的 ...

  6. Lua中如何实现类似gdb的断点调试—08支持通过包名称添加断点

    在前一篇中我们支持了通过函数名称来添加断点,我们同时也提到了在Lua中一个函数的名称的并不是确定的.准确的说,Lua中的函数并没有名称,所谓名称其实是保存这个函数值的变量的名称. 于是通过函数名称添加 ...

  7. Lua中如何实现类似gdb的断点调试--05优化断点信息数据结构

    在上一篇04优化钩子事件处理中,我们在钩子函数中引入了call和return事件的处理,对性能进行了优化. 细心的同学可能已经发现了,我们的hook函数中call事件和line都需要对整个断点表进行遍 ...

  8. Lua中如何实现类似gdb的断点调试—06断点行号检查与自动修正

    前面两篇我们对性能做了一个优化,接下来继续来丰富调试器的特性. 我们前面提到过,函数内并不是所有行都是有效行,空行和注释行就不是有效行.我们之前在添加断点的时候,并没有对行号进行检查,任何行号都能成功 ...

  9. linux下的gdb调试工具--断点调试

    到目前为止我们的调试手段只有一种: 根据程序执行时的出错现象假设错误原因,然后在代码中适当的位置插入printf,执行程序并分析打印结果,如果结果和预期的一样,就基本上证明了自己假设的错误原因,就可以 ...

随机推荐

  1. 中文分词,自然语言处理器NLP。 六月份版本已上线。

    一,  没有对比,就没有伤害,我们分词的优势在哪里?走一波测试. 跑一下CaCl2,看看效果. 二   想要分什么词汇,自己自定义即可. 目前每个月都会出一个新的版本,主要是和金融相关的词汇. 这是6 ...

  2. maven 中的工程依赖和层级依赖?

    一.什么是工程依赖? 思考问题?1.1一旦开始分模块开发的时候,之前的所有包都会被拆分成一个一个的项目 model mapper service ... 其实mapper需要model的支持,怎么解决 ...

  3. c#代码设计:子类和父类

    哭辽,事情是这样的 我想写个产品类用来放点相机参数,想类似这种的使用方式:(时间关系不改了,产品=Zoo,animals=相机) Zoo Zooxx= new Zoo (); Zoo.Animals ...

  4. CF Round #687 Div2 简要题解

    题面 A 可以发现,最远的几个人一定是 \((1, 1), (1, m), (n, 1), (n, m)\) 中的一个,直接计算即可. B 注意到颜色数量很少,直接暴力枚举最终的颜色后模拟即可. C ...

  5. swwager的使用

    最近弄swwager文档,被搞得恼火,故记录一下 先展示一下现有的页面,此页面由swwager自动生成 配置步骤: 一:导入swwager的依赖 <!-- =================== ...

  6. java中构造函数和一般函数的区别

    构造方法 特点: 1.方法名称和类名相同 2.不用定义返回值类型 3.不可以写return语句 作用: 给对象初始化 构造方法的细节: 当一个类中没有定义构造函数时,系统会默认添加一个无参的构造方法. ...

  7. imagenamed和imageWithContentOfFile的区别

    @implementation ViewController /** 图片的两种加载方式: 1> imageNamed: a. 就算指向它的指针被销毁,该资源也不会被从内存中干掉 b. 放到As ...

  8. 使用haproxy的ACL实现基于文件后缀名的动静分离

    一.环境准备 二.实现proxy [root@localhost ~]# yum -y install haproxy #创建子配置 [root@localhost ~]# mkdir /etc/ha ...

  9. find+grep+正则表达式

    目录 find+grep+正则表达式 1.find 2.grep 3.正则表达式 find+grep+正则表达式 1.find 根据文件的名称或者属性查找文件. # 自己在 /root/adc目录下长 ...

  10. SpringBoot是如何做到自动装配的

    背景 众所周知,如下即可启动一个最简单的Spring应用.查看@SpringBootApplication注解的源码,发现这个注解上有一个重要的注解@EnableAutoConfiguration,而 ...