日期: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??)