在前面两篇01最小实现02通用变量打印中,我们已经实现了设置断点、删除断点及通用变量打印接口。

本篇将继续新增两个辅助的调试接口:调用栈回溯打印接口、通用变量设置接口。前者打印调用栈的回溯信息,后者可以方便地修改变量的值,支持局部变量、upvalue以及全局的_ENV中的变量。

本文代码已开源至Github,欢迎watch/star。

本博客已迁移至CatBro's Blog,那里是我自己搭建的个人博客,欢迎关注。

调用栈打印函数

我们首先来实现调用栈回溯打印接口printtraceback(),这个接口比较简单,只是简单地包装了一下debug.traceback(),对层级进行了一个修正,就不多介绍了。

-- 打印调用栈的一个回溯
local function printtraceback(level)
-- 层次的默认值为1
-- 加上4是为了修正层次数以包含printtraceback, debug mainchunk, debug.debug, hook
level = (level or 1) + 4
print(debug.traceback(nil, level))
end

通用变量值修改函数

接着来实现通用的变量值修改函数_setvarvalue(),这个函数的结构跟_getvarvalue()类似,依次在局部变量、上值和_ENV表中查找,只不过找到之后会修改相应的值。函数有三个参数,name为要修改的变量的名字,value是修改的目标值,level指示在哪个层级的函数中查找,我们同样分成几部分来看。

local function _setvarvalue (name, value, level)
-- 省略
end

局部变量中查找

同样地,先处理层级,层级的默认值为1, 将层级加上1是为了将层次修正为包含_setvavalue函数自己。

然后遍历局部变量表,查找是否有名字为name的变量,如果找到的话记录其索引。注意 我们找到之后并没有立马跳出循环,因为可能具有多个同名的局部变量,我们应该获取索引最大的那个。

循环结束之后,如果已经在局部变量中找到了name,就修改该变量的值为value,然后返回"local",指示修改的是局部变量。

local function _setvarvalue (name, value, level)
local index -- 加1是为了将层次修正了包含_setvarvalue自身
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
index = i
-- 只更新索引值,并不退出循环
end
end
if index then
debug.setlocal(level, index, value)
return "local"
end
-- 省略
end

上值中查找

如果在局部变量中没有找到,我们再尝试到upvalue中进行查找。首先通过getbug.getinfo获取到第level层的函数,然后遍历其上值,如果找到匹配的变量就修改其值为value,然后返回"upvalue"以指示修改的是上值。

local function _setvarvalue (name, value, level)
-- 省略
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
debug.setupvalue(func, i, value)
return "upvalue"
end
end
-- 省略
end

_ENV表中查找

如果在普通的上值中还是没有找到,我们就去_ENV表中查找。首先调用_getvarvalue获取到_ENV表,注意这里的isenv标志为true。如果如果找到了_ENV表且表中存在名为name的变量,就修改其值为value,然后返回"global"以指示是修改的_ENV表中的值。如果没有_ENV表或表中不存在要找的变量,就返回nil

local function _setvarvalue (name, value, level)
-- 省略
local _, env = _getvarvalue("_ENV", level, true)
if env and env[name] then
env[name] = value
return "global"
else
return nil
end
end

包装函数

接下来我们同样再定义一个包装函数,对层次数level进行修正以包含setvarvalue函数自身以及其上层的debug mainchunkdebug.debug以及钩子函数。

然后对返回值进行检查,如果返回值为真,说明修改变量值成功,就打印变量类型,否则提示未找到。

local function setvarvalue (name, value, level)
-- level 默认值为1
-- 加4是为了将层次纠正为包含 settvarvalue, debug mainchunk, debug.debug和hook
level = (level or 1) + 4
local where = _setvarvalue(name, value, level)
if where then
print(where, name)
else
print(name, "not found")
end
end

接口定义好了,让我们把这两个接口也添加到返回到表中。

return {
-- 省略
printtraceback = printtraceback,
setvarvalue = setvarvalue,
}

测试脚本

接下来编写一个测试脚本test.lua以对我们新添加的接口进行测试。脚本很简单,就不多做解释了。

local ldb = require "luadebug"
local setbp = ldb.setbreakpoint
local rmbp = ldb.removebreakpoint
pv = ldb.printvarvalue
sv = ldb.setvarvalue
ptb = ldb.printtraceback g = 1 local u = 2
local function foo (n)
local a = 3
u = u
g = g
end local id1 = setbp(foo, 14) foo(10) rmbp(id1)

测试验证

我们运行测试脚本,首先调用堆栈打印函数,默认是打印从断点所在函数开始的堆栈。

$ lua test.lua
(local)foo test.lua:14
lua_debug> ptb()
stack traceback:
test.lua:14: in local 'foo'
test.lua:21: in main chunk
[C]: in ?
lua_debug>

我们显式指定层数试一下。

lua_debug> ptb(2)
stack traceback:
test.lua:21: in main chunk
[C]: in ?
lua_debug> ptb(0)
stack traceback:
./luadebug.lua:20: in hook '?'
test.lua:14: in local 'foo'
test.lua:21: in main chunk
[C]: in ?
lua_debug>

没有问题,层数为2的时候少打印了一层,为0的时候则多打了一层。

我们再来测试下变量值修改函数,先看来变量原来的值

lua_debug> pv("a")
local 3
lua_debug> pv("u")
upvalue 2
lua_debug> pv("g")
global 1
lua_debug>

然后修改变量的值,我们把这三个变量值都改成了6

lua_debug> sv("a", 6)
local a
lua_debug> sv("u", 6)
upvalue u
lua_debug> sv("g", 6)
global g
lua_debug>

然后再打印下值检查下结果,可以看到都修改成功了。

lua_debug> pv("a")
local 6
lua_debug> pv("u")
upvalue 6
lua_debug> pv("g")
global 6
lua_debug>

我们再试试显式指定层级为2,将变量值再改为8

lua_debug> sv("a", 8, 2)
a not found
lua_debug> sv("u", 8, 2)
local u
lua_debug> sv("g", 8, 2)
global g
lua_debug> pv("a")
local 6
lua_debug> pv("u")
upvalue 8
lua_debug> pv("g")
global 8
lua_debug>

变量a因为是函数foo的局部变量,所以外层看不到。变量u在main chunk中是属于局部变量,而变量g则还是全局变量。修改之后变量a的结果没有变,其他两个都改成了8。

Well done!

Lua中如何实现类似gdb的断点调试--03通用变量修改及调用栈回溯的更多相关文章

  1. Lua中如何实现类似gdb的断点调试--02通用变量打印

    在前一篇01最小实现中,我们实现了Lua断点调试的的一个最小实现.我们编写了一个模块,提供了两个基本的接口:设置断点和删除断点. 虽然我们已经支持在断点进行变量的打印,但是需要自己指定层数以及变量索引 ...

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

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

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

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

  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. service层 必须做业务逻辑的处理

    package com.aaa.zxf.service; import com.aaa.zxf.mapper.BookMapper; import com.aaa.zxf.model.Book; im ...

  2. 如何使用 pytorch 实现 yolov3

    前言 看了 Yolov3 的论文之后,发现这论文写的真的是很简短,神经网络的具体结构和损失函数的公式都没有给出.所以这里参考了许多前人的博客和代码,下面进入正题. 网络结构 Yolov3 将主干网络换 ...

  3. shell脚本检查域名证书是否过期

    最近公司的域名准备过期了,防止用户访问的时候出现异常,所以最近我们准备替换相关网站证书为最新的. (一般HTTPS证书有效期为1年,证书过期后或者该证书不是该域名的有效证书时,在浏览器中访问会出现如下 ...

  4. Redis 源码简洁剖析 09 - Reactor 模型

    Reactor 模型 事件驱动框架 Redis 如何实现 Reactor 模型 事件的数据结构:aeFileEvent 主循环:aeMain 函数 事件捕获与分发:aeProcessEvents 函数 ...

  5. JS 将Table内容导出到Excel(样式设计)

    转载请注明来源:https://www.cnblogs.com/hookjc/ function saveAsExcel(tableID){ var tb = new TableToExcel(tab ...

  6. NSArray基本概念

    1.NSArray的基本概念 什么是NSArray? NSArray是OC中的数组类,开发中建议尽量使用NSArray替代C语言中的数组 C语言中数组的弊端 int array[4] = {10, 8 ...

  7. Linux发行版·常见Linux系统下载

    本专题页汇总最受欢迎的Linux发行版基本介绍和下载地址,如果您是一位刚接触Linux的新手,这里的介绍可能对您有所帮助,如果您是以为Linux使用前辈,也可以在评论处留下您宝贵意见和经验,以便让更多 ...

  8. 【转载收藏】使用Jacoco远程统计tomcat服务的代码覆盖率

    在做软件开发的时候经常会遇到做各种测试,这里介绍一种本人遇到的:代码覆盖率测试, 这个测试非常实用,能发现许多无效的模块和代码.强烈推荐!!!!! 网上好多资料都不全,而且没有详细的配置流程 本文将简 ...

  9. iOS 屏幕录制实现

    iOS 屏幕录制实现 目录 iOS 屏幕录制实现 录屏API版本变化 App内部录制屏幕 录音麦克风声音 App内部录屏直播 Bonjour APP广播端实现 广播端App(直播平台)的实现 iOS1 ...

  10. Solution -「CF 1392H」ZS Shuffles Cards

    \(\mathcal{Description}\)   Link.   打乱的 \(n\) 张编号 \(1\sim n\) 的数字排和 \(m\) 张鬼牌.随机抽牌,若抽到数字,将数字加入集合 \(S ...