Lua中如何实现类似gdb的断点调试--01最小实现
说到Lua代码调试,最常用的方法应该就是加一堆print进行打印。print大法虽好,但其缺点也是显而易见的。比如效率低下,需要修改原有函数内部代码,在每个需要的地方添加print语句,运行一次只能获取一次信息,下次换个地方又得重新添加print语句。而且有时候,事先并不知道该去哪打印、或者打印什么内容,需要通过运行中获取的信息才能确定。
当print大法无法满足我们的需求时,就需要类似断点调试这样更高级的调试功能。本文将从零开始编写一个Lua调试器,实现类似gdb的断点调试功能。
本文代码已开源至Github,欢迎watch/star。
本博客已迁移至CatBro's Blog,那里是我自己搭建的个人博客,欢迎关注。
定义模块及接口
首先,我们来定义模块及接口,创建一个名为luadebug.lua
的模块,该模块是基于标准库中的debug库。为了实现最基本的断点调试功能,我们的模块提供了两个接口setbreakpoint
和removebreakpoint
,分别用于设置断点和删除断点。断点信息通过一个函数和一个行号指定,返回断点的id。后续可以通过这个id来删除相应断点。
#!/usr/bin/env lua
local debug = require "debug"
-- 省略...
local function setbreakpoint(func, line)
-- 省略...
end
local function removebreakpoint(id)
-- 省略...
end
return {
setbreakpoint = setbreakpoint,
removebreakpoint = removebreakpoint,
}
维护状态的数据结构
接着,来定义维护状态的数据结构,表status
维护了所有断点相关信息,其中的bpnum
元素表示当前总共有多少断点,bpid
表示当前的断点id,这个值是不断递增的,bptable
则是保存所有断点信息的表。表bptable
的键是断点的id,值也是一个表,保存了断点的所在的函数和行号。
-- 省略...
-- 记录断点状态
local status = {}
status.bpnum = 0 -- 当前总断点数
status.bpid = 0 -- 当前断点id
status.bptable = {} -- 保存断点信息的表
-- 省略...
设置断点接口
接下来来定义我们的setbreakpoint
接口。设置断点时,首先检查参数有效性,再更新断点id和断点数,然后将参数中传入的函数func
和行号line
保存到表bptable
中下一个断点id的位置。如果只有一个断点(从无到有),那么还需要调用debug.sethook
设置钩子。这是实现断点调试的核心函数之一,它使得我们有机会停在断点处。因为是最小实现,简单起见这里只设置了line
事件。
-- 设置断点
local function setbreakpoint(func, line)
if type(func) ~= "function" or type(line) ~= "number" then
return nil --> nil表示无效断点
end
status.bpid = status.bpid + 1
status.bpnum = status.bpnum + 1
status.bptable[status.bpid] = {func = func, line = line}
if status.bpnum == 1 then -- 第一个断点
debug.sethook(linehook, "l") -- 设置钩子
end
return status.bpid --> 返回断点id
end
钩子函数
在钩子函数中,通过debug.getinfo
获取到闭包信息,注意这里的层次为2,因为debug.getinfo()
函数本身的层次是0,钩子函数层次是1,断点所在的函数层次即为2。然后遍历断点表,与获取的闭包信息进行比较,如果函数和行号都匹配,说明命中断点。我们打印一行提示信息,然后调用debug.debug()
进入交互调试模式,debug.debug
是实现断点调试的另一个核心函数,它使得我们可以在断点处输入任意代码执行。交互调试模式一直持续,直到用户输入cont
为止。
-- 钩子函数
local function linehook (event, line)
local info = debug.getinfo(2, "nfS")
for _, v in pairs(status.bptable) do
if v.func == info.func and v.line == line then
local prompt = string.format("(%s)%s %s:%d\n",
info.namewhat, info.name, info.short_src, line)
io.write(prompt)
debug.debug()
end
end
end
删除断点接口
删除断点比较简单,首先检查id参数是否有效,如果无效直接返回,如果有效则将断点表中相应id位置的值置为nil即可,然后更新断点数,如果已经没有断点了,则清除钩子。
-- 删除断点
local function removebreakpoint(id)
if status.bptable[id] == nil then
return
end
status.bptable[id] = nil
status.bpnum = status.bpnum - 1
if status.bpnum == 0 then
debug.sethook() -- 清除钩子
end
end
至此我们的模块就编写好了,下面对这个模块进行测试。
测试脚本
我们编写一个如下的测试脚本test.lua
,定义了两个函数foo和bar,然后分别在两个函数中设置了一个断点(注意:注释和空行不是有效的断点行),然后多次调用函数并先后删除断点:
local ldb = require "luadebug"
local setbp = ldb.setbreakpoint
local rmbp = ldb.removebreakpoint
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, 11) -- 设置断点1
local id2 = setbp(bar, 16) -- 设置断点2
foo(10)
bar(10)
rmbp(id1) -- 删除断点1
foo(20)
bar(20)
rmbp(id2) -- 删除断点2
foo(30)
bar(30)
测试验证
然后我们运行测试脚本,可以看到程序停在了foo函数的断点1处。
$ lua test.lua
(local)foo test.lua:11
lua_debug>
我们可以在这里打印调用栈信息
$ lua test.lua
(local)foo test.lua:11
lua_debug> print(debug.traceback())
stack traceback:
(debug command):1: in main chunk
[C]: in function 'debug.debug'
./luadebug.lua:20: in hook '?'
test.lua:11: in local 'foo'
test.lua:22: in main chunk
[C]: in ?
lua_debug>
可以看到foo函数在第4层(第1层是执行我们调试命令的main chunk,第2层是debug.debug
函数,第3层是hook函数)。我们打印foo函数中第一个局部变量(即固定参数n)的值
lua_debug> print(debug.getlocal(4, 1))
n 10
lua_debug>
然后打印第二个局部变量(即a)的值
lua_debug> print(debug.getlocal(4, 2))
a 4
lua_debug>
然后我们输入cont
继续代码的执行,碰到了bar函数的断点2
lua_debug> cont
(local)bar test.lua:16
lua_debug>
我们打印bar函数的参数n的值,可以看到也是10
lua_debug> print(debug.getlocal(4, 1))
n 10
lua_debug>
然后我们输入cont
继续执行代码,因为断点1已经被移除,所以再次停在了bar函数的断点2处
lua_debug> cont
(local)bar test.lua:16
lua_debug>
我们再来打印下参数n的值,此时参数n的值是20
lua_debug> print(debug.getlocal(4, 1))
n 20
lua_debug>
我们再次输入cont
,因为断点2也被移除了,所以第三次调用foo
函数和bar
函数就没有再碰到断点,程序运行结束
lua_debug> cont
$
这样一个最简单的Lua断点调试器就完成了。虽然还比较简陋,但是已经能够应付一些简单的调试了。
Lua中如何实现类似gdb的断点调试--01最小实现的更多相关文章
- Lua中如何实现类似gdb的断点调试--04优化钩子事件处理
在第一篇的01最小实现中,我们实现了一个断点调试的最小实现,在设置钩子函数时只加了line事件,显然这会对性能有很大的影响.而后来两篇02通用变量打印和03通用变量修改及调用栈回溯则是提供了一些辅助的 ...
- Lua中如何实现类似gdb的断点调试—09支持动态添加和删除断点
前面已经支持了几种不同的方式添加断点,但是必须事先在代码中添加断点,在使用上不是那么灵活方便.本文将支持动态增删断点,只需要开一开始引入调试库即可,后续可以在调试过程中动态的添加和删除断点.事不宜迟, ...
- Lua中如何实现类似gdb的断点调试--02通用变量打印
在前一篇01最小实现中,我们实现了Lua断点调试的的一个最小实现.我们编写了一个模块,提供了两个基本的接口:设置断点和删除断点. 虽然我们已经支持在断点进行变量的打印,但是需要自己指定层数以及变量索引 ...
- Lua中如何实现类似gdb的断点调试--03通用变量修改及调用栈回溯
在前面两篇01最小实现及02通用变量打印中,我们已经实现了设置断点.删除断点及通用变量打印接口. 本篇将继续新增两个辅助的调试接口:调用栈回溯打印接口.通用变量设置接口.前者打印调用栈的回溯信息,后者 ...
- Lua中如何实现类似gdb的断点调试—07支持通过函数名称添加断点
我们之前已经支持了通过函数来添加断点,并且已经支持了行号的检查和自动修正.但是通过函数来添加断点有一些限制,如果在当前的位置无法访问目标函数,那我们就无法对其添加断点. 于是,本篇我们将扩展断点设置的 ...
- Lua中如何实现类似gdb的断点调试—08支持通过包名称添加断点
在前一篇中我们支持了通过函数名称来添加断点,我们同时也提到了在Lua中一个函数的名称的并不是确定的.准确的说,Lua中的函数并没有名称,所谓名称其实是保存这个函数值的变量的名称. 于是通过函数名称添加 ...
- Lua中如何实现类似gdb的断点调试--05优化断点信息数据结构
在上一篇04优化钩子事件处理中,我们在钩子函数中引入了call和return事件的处理,对性能进行了优化. 细心的同学可能已经发现了,我们的hook函数中call事件和line都需要对整个断点表进行遍 ...
- Lua中如何实现类似gdb的断点调试—06断点行号检查与自动修正
前面两篇我们对性能做了一个优化,接下来继续来丰富调试器的特性. 我们前面提到过,函数内并不是所有行都是有效行,空行和注释行就不是有效行.我们之前在添加断点的时候,并没有对行号进行检查,任何行号都能成功 ...
- linux下的gdb调试工具--断点调试
到目前为止我们的调试手段只有一种: 根据程序执行时的出错现象假设错误原因,然后在代码中适当的位置插入printf,执行程序并分析打印结果,如果结果和预期的一样,就基本上证明了自己假设的错误原因,就可以 ...
随机推荐
- javaObject类-equals方法及覆盖
1 package face_object; 2 /* 3 * Object:所有类的根类. 4 * Object是不断抽取而来的,具备所有对象都具备的共性内容. 5 * 常用的共性功能: 6 * 7 ...
- python30day
内容回顾 tcp协议的多人多次通信 和一个人通信多说句话 和一个人聊完再和其他人聊 bind 绑定一个id和端口 socket()tcp协议的server listen 监听,代表socket服务的开 ...
- 开发升讯威在线客服系统启示录:怎样编写堪比 MSDN 的用户手册
本系列文章详细介绍使用 .net core 和 WPF 开发 升讯威在线客服与营销系统 的过程. 免费在线使用 & 免费私有化部署:https://kf.shengxunwei.com 文章目 ...
- flask学习1
总结 Flask Web 框架 轻量 websocket 全双工通讯 socket TCP 通讯 MongoDB 数据库 文件型数据库 {} 没有限制 和 约束 Mui + HTML5 Plus 调用 ...
- 抽签小程序(C语言随机数)
最近班级里需要人员抽签参加活动,闲来无事用java的(Math.random()方法||java.util.Random())写了一个随机抽签的,所以我又了解了一下C语言的随机数获取. C语言的随机数 ...
- 什么是UIImageView
UIKit框架提供了非常多的UI控件,但并不是每一个都很常用,有些控件可能1年内都用不上,有些控件天天用,比如UIButton.UILabel.UIImageView.UITableView等等 UI ...
- 无意进去UIView随笔闹腾着玩 -by 胡 xu
1 @interface UIView : UIResponder<NSCoding, UIAppearance, UIAppearanceContainer, UIDynamicItem> ...
- 社交网络分析的 R 基础:(六)绘图操作
R 语言强大的可视化功能在科学研究中非常受欢迎,丰富的类库使得 R 语言可以绘制各种各样的图表.当然这些与本章内容毫无关系,因为笔者对绘制图表了解有限,仅限于能用的程度.接下来的内容无需额外安装任何包 ...
- python——虚拟环境管理大合集
个人常用:pipenv 安装 pip3 install pipenv 创建虚拟环境 # 默认安装在~/.local/virtualenv下 mkdir project cd project pipen ...
- 什么是rest?restful?
百度百科解释: rest:REST即表述性状态传递(英文:Representational State Transfer,简称REST)是Roy Fielding博士在2000年他的博士论文中提出来的 ...