日期:2014.7.29
PartⅢ The Standard Libraries
24 The Debug Library
Lua的debug库并不是提供一个调试器,而是提供一些供你写调试器的一些操作。Lua实现这些功能是以C API来实现的,因此这个库相当于提供使用Lua代码访问C API,这是从性能上考虑的。
Lua的debug库包含两类函数:introspective function和hooks(啥东西?)。前面一个函数允许我们对调试正在运行的程序(当前运行到的行数、局部变量的名字和值等);hook则允许我们回溯程序的运行过程。
Debug库一个重要的概念是:stack level。指的是一个代表当前阶段一个特定函数的活跃值:访问debug库的函数其level为1,调用该函数的函数其level为2。
24.1 Introspective Facilities
debug库中主要的内省函数是debug.getinfo。该函数的第一个参数可以是一个函数也可以是一个stack level。当参数是一个函数的时候,该函数返回一个table包含该函数的一些信息:
当参数函数为一个C函数的时候,返回信息仅包括函数类型(Lua C)、名字、函数名的前一个字段(global local 等)
当以数值作为参数调用该函数的时候,该数值代表一个stack level。此时会得到一个满足该level的函数的信息。如当n为1的时候,会得到进行执行调用的函数,当为0的时候,得到getinfo这个函数本身,当n大于stack中活跃函数的数量的时候,返回nil。when you query an active function,calling debug.getinfo with a number,the result table has an extra field
,currentline,with the line where the function is at that moment.Moreover,func has the function that is active at that level.
table中的字段name需要谨慎对待,因为lua中的函数可以没有名字,也可以有多个名字。
作者也提到,getinfo函数不怎么高效。lua以一种不损害程序运行的形式保存debug的信息,efficient retrieval is a secondary goal here.getinfo函数接受一个第二参数用来选择需要得到的信息以提高效率,此时函数不会去收集用户不需要的信息。该参数的类型是一个字符串型,一个字母收集一类字段的信息:
'n' 收集 name 和 namewhat
'f' 收集 func
'S' 收集source,short_src,what,linedefined,和lastlinedefined
'l' 收集 currentline
'L' 收集 activeline
'u' 收集 nup
下面的例子介绍了debug.getinfo函数的使用:
Accessing local variables
可以使用debug.getlocal 函数查看任意活跃函数中的局部变量,这个函数有两个参数:访问的函数的stack level和访问变量的index值。该函数返回两个值:访问变量的名字和值。如果参数index值大于函数中总的变量的值,那么getlocal函数返回nil。如果stack level值不符合要求,则会引起错误(此时的技巧是通过getinfo函数来先验证stack level)
函数中的局部变量依据其出现的顺序进行排序,最先出现的变量其index值最小,而且只会对当前函数有效区间内的变量进行计数,举例来看:
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,29)
打印出来的值为:
a 10
b 29
x nil
a 4
函数中首先出现的局部变量是a,其index为1,其次是b,index为2,接着是x,index为3,最后为函数体内部的局部变量a,其index为4.此时要注意的是,函数体内的局部变量c,有其自身的有效范围,此时已经在函数体的有效范围之外了。(局部变量只会在其初始化代码之后可见)。
自Lua5.2开始,负数作为参数会得到函数的一些额外信息:index -1代表第一个额外信息,此时这个name总是"(*vararg)".---这段内容还有待考证
也可以通过函数debug.setlocal来改变局部变量的值,该函数的前两个参数分别为:stack level 和变量的index值,第三个参数则为要设定的新的值。如果index超出了范围,则该函数会返回nil值
Accessing non-local variables
使用一个lua函数:getupvalue 可以使我们访问非局部变量。与局部变量不同的是,非局部变量是当函数非active的时候才会存在(这也是所谓的闭包),因此该函数的第一个参数不是一个stack level ,而是一个函数(准确的说是一个闭包),第二个参数是变量的index值,其顺序和局部变量一致都是先出现的index值越小。(函数不能以同一个名字访问两个非局部变量)
使用setupvalue 可以用来改变非局部变量的值,与setlocal一样,函数也是三个参数,只不过第一个参数是一个闭包。
Accessing other coroutines
dubug库中的所有内省函数都接受一个可选参数--一个协同程序作为其第一参数,因此我们也可以从外部查看协同程序,例如:
co = coroutine.create(function ( ... ) local x = 10 coroutine.yield() error("some error") end) coroutine.resume(co) print(debug.traceback(co))
运行该段代码打印出:
stack traceback:
[C]: in function 'yield'
此时在yield函数内部
回溯不会执行resume,因为协同程序和主程序不在同一个栈中。
当协同程序引发错误的时候,不会存在与当前的这个栈中。这就意味着我们可以在其引发的错误之后查看该协同程序,继续上述的例子,假如我们此时再一次resume协同程序,这次便会引发错误:
e.g. coroutine.resume(co) print(debug.traceback(co)) print(coroutine.resume(co))
--打印
stack traceback:
[C]: in function 'yield'
false some error
而此时再一次回溯:
e.g. coroutine.resume(co) print(debug.traceback(co)) print(coroutine.resume(co)) print(debug.traceback(co))
--打印
stack traceback:
[C]: in function 'yield'
false some error
stack traceback:
[C]: in function 'error'
此时便会到error函数内部了
我们甚至可以再函数产生错误之后再范围协同程序内部的局部变量
print(debug.getlocal(co,1,1))
--打印
x 10 --这个是协同程序内部的局部变量x的值
24.2 Hooks
Lua debug库的hook机制允许我们注册一个函数的调用,然后在程序运行的某个特定时间点调用我们注册的那个函数。这里有四个时间点(events 事件?)可以触发一个hook:
call events 每次lua调用一个函数的时候就触发这个事件
return events 每次函数返回值的时候触发这个事件
line events 每当lua开始执行新的一行代码的时候触发这个事件
count events 进过给定数量的指令后触发这个事件
Lua通常以一个参数调用hooks,这个参数是字符串型,代表某个事件:"call"("tail call"),"return","line","count".当参数是line的时候,函数也接受新的一行的行数作为第二个参数。而如果需要得到更多关于hook内部的信息,则需要使用函数:debug.getinfo.
使用函数debug.sethook 可以注册一个hook,参数为两个可选第三个参数:第一个参数是要注册的函数;第二个参数是一个string字符串型,代表需要触发的事件('c','r','l'); 可选的第三参数代表我们想获得count event事件的频率;call,return,line事件通过第二个参数控制,而可选的第三个参数用来控制count事件。关掉一个hook,则以无参数调用sethook。
24.3 Profiles
Lua的debug库不仅可以用来做debug,还可以用来(profiling??)