【Lua进阶系列】之Debug库
大家好,我是Lampard~~
欢迎来到Lua进阶系列的博客
前文再续,书接上一回。今天和大家分享一下lua中debug库的使用
一.首先什么是debug库?
以下是官方定义:
Lua 本身并未有内置的调试器,但是它提供了 debug 库用于提供创建我们自定义调试器的功能,debug库并不给你一个可用的Lua 调试器,而是给你提供一些为Lua写一个调试器的方便。
简而言之,debug库给我们开发者提供了构建调试器的工具。
二.自省(introspective)函数
debug库由两种函数组成:自省(introspective)函数和hooks。自省函数使得我们可以检查运行程序的某些方面,比如活动函数栈、当前执行代码的行号、本地变量的名和值。Hooks钩子可以跟踪程序的执行情况。
(1)introspective自省debug.getinfo函数
调试库中主要的自省函数是debug.getinfo和debug.getlocal函数。它的第一个参数可以是一个函数或者一个栈层。当为某函数foo调用debug.getinfo(foo)时,就会得到一个table,其中包含了一些与该函数相关的信息。这个table中的字段有以下几种:
- source,标明函数被定义的地方。如果函数在一个字符串内被定义(通过 loadstring),source 就是那个字符串。如果函数在一个文件中定义,source 是@加上文件名
- short_src,source 的简短版本(最多 60 个字符),记录一些有用的错误信息。
- linedefined,source中函数被定义之处的行号。
- lastlinedefined:该函数定义的源代码中最后一行的行号
- what:函数的类型。如果foo是一个普通的Lua函数,则为“Lua”;如果是一个C函数,则为“C”;如果是一个Lua程序块(chunk)的主程序部分,则为main
- name:该函数的一个适当的名称
- namewhat:上一个字段的含义。它可能是global、local、method、field或空字符串。空字符串表示Lua没有找到该函数的名称
- nups:该函数的upvalue的数量
- avtivelines:一个table,包含了该函数的所有活动行的集合。所谓活动行就是含有代码的行,这是相对于空行和注释行而言的
- func:函数本身
当foo是一个C函数的时候,Lua无法知道很多相关的信息,所以对这种函数,只有what、name、namewhat这几个域的值可用
(2)栈级别(stack level)
Debug库中的一个重要的思想是栈级别(stack level)。一个栈级别就是一个指向在当前时刻正在活动的特殊函数的数字,也就是说,这个函数正在被调用但还没有返回。调用debug库的函数级别为 1,调用他(他指调用debug库的函数)的函数级别为2,以此类推。以数字 n调用debug.getinfo(n)时,返回在n级栈的活动函数的信息数据。比如,如果n=1,返回的是正在进行调用的那个函数的信息。(n=0表示 C函数getinfo本身)如果n比栈中活动函数的个数大的话,debug.getinfo返回nil。当你使用数字n调用debug.getinfo查 询活动函数的信息的时候,返回的结果table中有一个额外的域:currentline,即在那个时刻函数所在的行号。另外,func表示指定n级的活动函数。
(3)提高调用getInfo的效率
函数getinfo 的效率并不高。Lua以不消弱程序执行的方式保存debug信息(Lua keeps debug information in a form that does not impair program execution),效率被放在第二位。为了获取比较好地执行性能,getinfo可选的第二个参数可以用来指定选取哪些信息。指定了这个参数之后,程 序不会浪费时间去收集那些用户不关心的信息。这个参数的格式是一个字符串,每一个字母代表一种类型的信息,可用的字母的含义如下
'n': 得到name和namewhat字段 (selects fields name and namewhat)
'f' : 输出函数本身(selects field func)
's' : 输出标明函数被定义的地方,函数所在的行号(selects fields source, short_src, what, and linedefined)
'l' : 输出在那个时刻函数所在的行号(selects field currentline)
'u' :输出该函数的upvalue的数量 (selects field nup)
function traceback ()
local level = 1
while true do
local info = debug.getinfo(level, "Sl")
if not info then break end
if info.what == "C" then -- is a C function?
print(level, "C function")
else -- a Lua function
print(string.format("[%s]:%d",info.short_src, info.currentline))
end
level = level + 1
end
end
不难改进这个函数,使得getinfo获取更多的数据,实际上debug库提供了一个改善的版本debug.traceback,与我们上面的函数不同的是,debug.traceback并不打印结果,而是返回一个字符串
(4)introspective自省debug.getlocal函数
调用debug库的getlocal函数可以访问任何活动状态的局部变量。这个函数由两个参数:将要查询的函数的栈级别和变量的索引。函数有两个返回值: 变量名和变量当前值。如果指定的变量的索引大于活动变量个数,getlocal返回nil。如果指定的栈级别无效,函数会抛出错误。(你可以使用 debug.getinfo检查栈级别的有效性),Lua对函数中所出现的所有局部变量依次计数,只有在当前函数的范围内是有效的局部变量才会被计数
三.Hooks钩子
debug库的hook是这样一种机制:注册一个函数,用来在程序运行中某一事件到达时被调用。有四种可以触发一个hook的事件:当Lua调用一个函数的时候call事件发生;每次函数返回的时候return事件发生;Lua开始执行函数的新行时候line事件发生;运行指定数目的指令之 后,count事件发生。Lua使用单个参数调用hooks,参数为一个描述产生调用的事件:"call"、"return"、"line" 或 "count"。另外,对于line事件,还可以传递第二个参数:新行号。我们在一个hook内总是可以使用debug.getinfo获取更多的信息。
使用带有两个或者三个参数的debug.sethook 函数来注册一个hook:第一个参数是hook函数;第二个参数是一个描述我们打算监控的事件的字符串;可选的第三个参数是一个数字,描述我们打算获取 count事件的频率。为了监控call、return和line事件,可以将他们的第一个字母('c'、'r' 或 'l')组合成一个mask字符串即可。要想关掉hooks,只需要不带参数地调用sethook即可。
例如:最简单的trace,仅仅打印每条执行语句的行号:
debug.sethook(print, "l")
显示结果如下:
line 136
line 113
line 76
line 77
line 113
line 118
我们也可以自定义一个handler,传入第一个参数,通过debug库的getinfo获取正在执行的代码文件路径:
debug.sethook(function (event, line)
print(debug.getinfo(2).short_src .. ":" .. line)
end, "l")
显示结果如下:
/usr/local/share/xmake/core/base/path.lua:46
/usr/local/share/xmake/core/base/path.lua:47
/usr/local/share/xmake/core/base/path.lua:56
/usr/local/share/xmake/core/base/string.lua:32
/usr/local/share/xmake/core/base/string.lua:33
/usr/local/share/xmake/core/base/string.lua:34
/usr/local/share/xmake/core/base/string.lua:35
/usr/local/share/xmake/core/base/string.lua:36
/usr/local/share/xmake/core/base/string.lua:38
/usr/local/share/xmake/core/base/string.lua:33
如果需要禁用之前的hook,只需要调用:
debug.sethook()