调试库中的钩子机制允许用户注册一个钩子函数,这个钩子函数会在程序运行中某个特定事件发生时被调用。有四种事件能够触发一个钩子:
- 每当调用一个函数时产生的
call
事件 - 每当函数返回时产生的
return
事件 - 每当开始执行一行新代码时产生的
line
事件 - 执行完指定数量的指令后产生的
count
事件。(这里的指令指的是内部操作码)
Lua
语言用一个描述导致钩子函数被调用的事件的字符串为参数来调用钩子函数,包括 "call"
(或 "tail call"
)、 "return"
、 "line"
或 "count"
。对于 line
事件来说,还有第二个参数,即新行号。我们可以在钩子函数内部调用函数 debug.getinfo
来获取更多的信息。
要注册一个钩子,需要用两个或三个参数来调用函数 debug.sethook
:第一个参数是钩子函数,第二个参数是描述要监控时间的掩码字符串,第三个参数是一个用于描述以何种频度获取 count
事件的可选数字。如果要监控 call
、 return
和 line
事件,那么只需要把这几个事件的首字母( c
、 r
或 l
)放入掩码字符串。如果要监控 count
事件,则只需要在第三个参数中指定一个计数器。如果要关闭钩子,只需不带任何参数地调用函数 sethook
即可。
作为一个简单的示例,以下代码安装了一个简单的跟踪器,它会输出解释器执行的每一行代码:
debug.sethook(print, "l")
这句调用只是简单地把函数 print
安装为一个钩子函数,并告诉 Lua
语言在 line
事件发生时调用它。一个更精巧的跟踪器可以使用函数 getinfo
获取当前文件名并添加到输出中:
function trace (event, line) local s = debug.getinfo(2).short_src print(s .. ":" .. line) end debug.sethook(trace, "l")
与钩子一起被使用的一个很有用的函数是 debug.debug
。这个简单的函数可以提供一个能够执行任意 Lua
语言命令的提示符,其等价于如下的代码:
function debug1() while true do io.write("debug> ") local line = io.read() if line == "cont" then break end assert(load(line))() end end
当用户输入命令 "cont"
时,函数返回。这种标准的实现十分简单,并且在全局环境中运行命令,位于正在被调试代码的定界范围之外。