24.1 Lua debug library

Debug库并没有为你提供一个Lua的调试器,而是为你提供了编写个人调试器的基本功能。出于性能的考虑,调用基本功能的正式接口都是通过C API提供。在Lua中访问debug库的一种方式是通过Lua代码直接访问。

和其他库不同,你要很吝啬的使用debug库。首先,库中的很多功能都会影响性能。其次,它打破了Lua中一些颠扑不破的真理,比如在一个局部变量的作用域之外,你不能访问它。最后,你可以不会想在产品发布的最后版本中包含它们,或者你可能想出去它们。

Debug库包含两大函数:内省函数(introspective functions)和hooks(钩子)。内省功能允许我们监视运行程序的几个方面,比如它活动函数的栈,当前的执行行,以及局部变量的值和名称。钩子运行我们跟踪程序的执行。

Debug库的一个很重要的概念是栈深度(stack level),栈深度用一个数字表示当前活动函数的栈:函数调用的debug库是level 1,调用函数本身是level 2,等等。

内省函数

debug库中的主要内省函数是debug.getinfo。它的第一个参数可以是函数或者一个栈深度值。当你为一些函数foo,使用debug.getinfo(foo)的时候,你将得到一个table,table中存放着这个函数的一些数据。table有如下几个域:(译者注:不是在所有时候,table中都包含这些域。)
source: 函数定义的位置。如果函数定义在字符串中(通过loadstring载入),source就是string。如果函数定义在一个文件中,source表示为带有'@'前缀的文件名。
short_src: source的一个简短表示(最多60个字符),在错误信息时使用。
linedefined: 源码中,函数定义的首行行号。
lastlinedefined: 源码中,函数定义的最后一行的行号。
what: 函数的类型。选项为”Lua",如果函数foo是一个lua函数,"C"如果是一个C函数,或者是"main“如果是Lua程序的主要部分。
name: 一个合适的函数名字。
namewhat: 前面一个域的含义。可以是”global“,”local“,”method“,”field“或者”“(空字符串)。空字符串表示Lua没有找到该函数的名称。
nups: 该函数upvalue的个数。
activelines: 是一个table,包含该函数活动行的集合。活动行是相对于空行或者注解行而言的,包含代码的行。(通常用于设置断点。大多数调试器不允许你在非活动行设置断点,因为在调试模式下接触不到非活动行。)
func: 函数本身;详细请见后面。

当foo是一个C函数时,Lua包含它的信息比较少。对于大多数C函数而言,只包含几个有关的域what,name,namewhat。

当对一些数字n调用debug.getinfo(n)的时候,你会得到当前栈中,栈深度值对应的活动函数。例如n是1,你会得到正在执行的函数。如果n是0,你会得到一个C函数getinfo函数本身。如果n超出了栈的深度,debug.getinfo返回nil。当你用一个数字调用debug.getinfo,查询一个活动函数时,做为结果返回的table,包含一个外额的域,currentline,表示当前函数所在行。此外,func中包含深度值所对应的函数。

name域很特别。因为Lua中的函数是first-class,一个函数可能没有名字,或者有多个名字。Lua查找函数名的方法为:通过代码中调用此函数的情况来决定。此方法只有当用数字调用getinfo函数时才起作用,也就是说,只有当我们要查看一具体调用的函数信息时才起作用。

getinfo函数效率很低。效率是放在第二位的,重要的是Lua尽可能的让调试信息不影响程序的执行。但是为了获得更好的性能,getinfo提供了一个可选择的第二参数。由此,getinfo就不需要对用户提供他们不需要的数据,从而节省了时间。第二参数的格式是字符串,字符串中的每个字母对应要获取的一个域,根据如下的表:
'n'   获取name和namewhat
'f'    获取func
'S'   获取source, short_src, what, linedefined, 和lastlinedefined
'l'    获取当前行
‘L’   获取活动行
'u'   获取nup (upvalue的个数) 

下面的函数用于解释debug.getinfo,函数输出活动栈的回溯:
function traceback()
    for level = 1, math.huge do
        local info = debug.getinfo(level, "nSl")
        if not info then break end
        if info.what == "C" then -- is a C function?
            print(level, "C function")
        else
            print(info.name,string.format("%d, [%s] : %d", level, info.short_src, info.currentline))
        end
    end
end

通过从getinfo中获取更多的数据,可以很容易的改进这个函数。实际上,debug库已经提供了一个改进的版本,traceback函数。和我们写的版本不同的是,debug.traceback函数没有输出结果,而是返回一个表示回溯结果的字符串(通常很长)。

访问局部变量

我们可以通过debug.getlocal监视任何活动函数的局部变量。这个函数有两个参数:你希望查询函数的栈深度值和变量索引。它返回两个值:变量的名称和变量的值。如果变量的索引大于活动变量的数量,getlocal返回nil。如果栈深度值非法,函数就会引发一个错误。(我们可以通过debug.getinfo来检查栈深度值的有效性。)

Lua按照局部变量在函数中出现的顺序给变量标号,只统计函数中在当前作用域的活动变量。例如,以下函数:
function foo(a, b)
     local x
     do local c = a - b end
     local a = 1
     while true do
          local name, value = debug.getlocal(1, a)
          if not name then break end
          print(name, value)
          a = a + 1
     end
end

调用函数foo(10, 20)会得到:
a      10
b      20
x      nil
a      4
索引是1的变量是a,b的索引是2,x的索引是3,另一个a是4. getlocal的位置在超出了变量c的作用域。同时name和value变量还没有在作用域里面(注意,局部变量只在它初始化以后的代码中才可见。)

从Lua 5.2开始负索引可以得到函数额外参数的信息:索引 -1 指向第一个额外参数,这情况下,参数的名字总是“(*vararg)”。

你也可以用debug.setlocal修改局部变量的值。它的头两个参数和getlocal的一样,分别是栈深度值和变量索引。第三个参数是要赋给这个变量的值。函数返回变量的名称,如果变量在作用域外就返回nil。

访问非局部变量

debug库提供一个Lua函数getupvalue,允许我们通过它来访问非局部变量。和局部变量不同,非局部变量被一个存在的函数引用,即使这个函数不处于活动状态(基本上指的是闭包)。因此函数的第一个参数不是栈深度值,而是一个函数(更准确的说是一个闭包)。第二个参数是变量索引。Lua按照非局部变量被函数头一次引用的顺序给他们标号,但这个顺序是不相关的,因为一个函数不能访问两个使用同一个名字的非局部变量。

你可以通过debug.setupvalue更新非局部变量。可能正如你想的那样,这个函数有三个参数:一个闭包,一个变量索引,和一个新值。和setlocal函数类似,setupvalue函数返回这个变量的名字,如果超出了索引变量的作用域就返回nil。
function getvarvalue(name, level)
     local value
     local found = false
     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 value end

     -- try non-local variables

     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 v end
     end

     -- not found; get value from the environment
     local env = getvarvalue("_ENV", level)
     return env[name]
end
代码24.1显示了访问调用函数的任何变量。level为函数要查找的栈深度值;+1是因为当前调用栈中包含了getvarvalue本身。函数getvarvalue开始的时候,试着查找局部变量。如果出现重名的局部变量,则选择索引值最大的那个;因此循环总是执行到最后。如果不能找到要查找的变量,然后就到非局部变量中找。为此,它用debug.getinfo调用闭包函数,然后遍历它的非局部变量(译者注:这里的闭包函数就是函数本身,用getupvalue得到上一层函数的局部变量,即本函数的非局部变量)。最后,如果非局部变量中还找不到这个变量,就会到全局变量里找:它以_ENV为参数递归调用自己,到环境中去找。

访问其他协程
debug库中的所有内省函数都可以将一个协程作为它们的第一个参数。由此它们可以在协程的外部对协程进行监视。例如下面的例子:
co = coroutine.create( function ()
     local x = 10
     coroutine.yield()
     error("some error")
end)

coroutine.resume(co)
print(debug.traceback(co))

函数co会对协程co输出如下结果:

stack traceback:
     [C]: in function 'yield'
     temp:3: in function

跟踪信息并没有到达resume函数,因为协程和主程序运行在不同的栈上。

当协程引发一个错误时,它不会清除释放它的栈。这意味着我们可以在它发生错误之后监视它。接着上面的例子,如果我们重新resume上面的协程,它会触发一个错误:

     print(coroutine.resume(co)) --> false      temp:4: some error

我们会得到如下的回溯信息:

     [C]: in function 'error'
     temp:4: in function

我们也可以监视协程中的局部变量,即使在它错误以后:

     print(debug.getlocal(co, 1, 1))      --> x      10

你可能感兴趣的:(lua)