Lua 除錯庫
阿新 • • 發佈:2018-12-21
轉載:http://www.cnblogs.com/lsgxeva/p/7785211.html
Q:什麼是活動函式?
A:程式中被呼叫但還未執行完成的函式。
function g() --[[ 此時函式"g"被呼叫但還未執行完成,是活動函式。所以這裡獲取的是函式"g"的資訊。 "debug.getinfo(2)"獲取的才是函式"f"的資訊。]] local x = debug.getinfo(1, "n") for k, v in pairs(x) do print(k, v) end end function f() -- 此時函式"f"被呼叫但還未執行完成,是活動函式。所以這裡獲取的是函式"f"的資訊。 local x = debug.getinfo(1, "n") for k, v in pairs(x) do print(k, v) end print() g() end f() --[[ result: namewhat global name f namewhat global name g ]]
Q:什麼是呼叫棧?
A:Lua儲存活動函式所使用的棧。每個執行緒都有自己獨立的呼叫棧。
Q:什麼是呼叫棧的級別?
A:呼叫除錯庫函式的函式的棧級別是1,呼叫該函式的函式的棧級別是2,以此類推。
function foo() -- 呼叫除錯庫的函式。 ... end function goo() foo() end function hoo() goo() end --[[ 被呼叫的除錯庫函式棧級別為0,"foo"的棧級別為1,"goo"的棧級別為2,"hoo"的棧級別為3。 如果還有別的函式呼叫"hoo()",則棧級別以此類推。]] hoo()
Q:如何檢視呼叫棧資訊?
A:
--[[ debug.traceback([thread,] [message [, level]]) 首先列印"message",接著從第"level"個棧級別開始列印"thread"執行緒中的呼叫棧資訊。 如果"message"不是字串或"nil",則函式不做任何處理直接返回"message"。 "thread"預設為當前執行緒,"level"預設為1。]] function foo() print(debug.traceback("This is traceback: ")) print() print(debug.traceback("Traceback from stack_level 2: ", 2)) print() print(debug.traceback({})) end function goo() foo() end function hoo() goo() end hoo() --[[ results: This is traceback: stack traceback: E:\a.lua:2: in function 'foo' E:\a.lua:8: in function 'goo' E:\a.lua:12: in function 'hoo' E:\a.lua:15: in main chunk [C]: in ? Traceback from stack_level 2: stack traceback: E:\a.lua:8: in function 'goo' E:\a.lua:12: in function 'hoo' E:\a.lua:15: in main chunk [C]: in ? table: 00522F78 ]]
Q:如何檢視函式資訊?
A:
--[[ debug.getinfo([thread,] f [, what])
返回一個"table",其中包含執行緒"thread"中的函式"f"由"what"指定的相關資訊。
"thread"預設為當前執行緒。"f"可以是函式名,也可以是一個數值,如果是數值則代表該函式的棧級別。
如果通過名字指定的函式不存在,則報錯;如果通過數值指定的函式不存在,則返回"nil"。
如果"what"不指定,預設情況下返回除合法行號表外的所有域:
source: 建立這個函式的"chunk"的名字。
如果"source"以'@'打頭,表示這個函式定義在一個檔案中,而'@'之後的部分就是檔名。
若"source"以'='打頭,表示之後的部分由使用者行為來決定如何表示原始碼。
其它的情況下,這個函式定義在一個字串中,而"source"正是那個字串。
short_src: 一個“可列印版本”的"source",用於出錯資訊。
linedefined: 函式定義開始處的行號。
lastlinedefined: 函式定義結束處的行號。
what: 如果函式是一個Lua函式,則為一個字串"Lua";
如果是一個C函式,則為"C";
如果是一個"chunk"的主體部分,則為"main"。
currentline: 給定函式正在執行的那一行。當提供不了行號資訊的時候,"currentline"被設為-1。
name: 給定函式的一個合理的名字。
因為Lua中的函式是"first-class values",所以它們沒有固定的名字。
一些函式可能是全域性複合變數的值,另一些可能僅僅只是被儲存在一個"table"的某個域中。
Lua會檢查函式是怎樣被呼叫的,以此來找到一個適合的名字。
如果它找不到名字,該域就被設定為"NULL"。
namewhat: 用於解釋"name"域。
其值可以是"global","local","method","field","upvalue",或是"",
這取決於函式怎樣被呼叫。(Lua用空串表示其它選項都不符合)
istailcall: 如果函式以尾呼叫形式呼叫,這個值為"true"。在這種情況下,當前棧級別的呼叫者不在棧中。
nups: 函式的"upvalue"個數。
nparams: 函式固定形參個數(對於C函式永遠是0)。
isvararg: 如果函式是一個可變引數函式則為"true"(對於C函式永遠為"true")。
func: 函式本身。
activelines: 合法行號表。
表中的整數索引用於描述函式中哪些行是有效行。
有效行指有實際程式碼的行,即你可以置入斷點的行。無效行包括空行和只有註釋的行。
"what"可以指定如下引數,以指定返回值"table"中包含上面所有域中的哪些域:
'n': 包含"name"和"namewhat"域;
'S': 包含"source","short_src","linedefined","lastlinedefined"以及"what"域;
'l': 包含"currentline"域;
't': 包含"istailcall"域;
'u': 包含"nup","nparams"以及"isvararg"域;
'f': 包含"func"域;
'L': 包含"activelines"域;]]
-- 簡易版"debug.traceback()"。
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
Q:如何除錯函式區域性變數資訊?
A:
--[[ debug.getlocal([thread,] f, local)
返回線上程"thread"中棧級別為"f"處函式的索引為"local"的區域性變數的名字和值。
"thread"預設為當前執行緒。此函式不僅用於訪問顯式定義的區域性變數,也包括形參、臨時變數等。
函式"f"中第一個形參或是定義的第一個區域性變數的索引為1,然後遵循在程式碼中定義的順序索引值遞增,
只計算函式當前作用域中的活動變數。
負索引代表可變引數。-1指第一個可變引數,以此類推。如果指定的"local"處沒有變數,則返回"nil"。
如果指定的"f"越界,則報錯。(你可以呼叫"debug.getinfo()"來檢查棧級別是否合法)
以'('開頭的變數名錶示沒有名字的變數(比如是迴圈控制用到的控制變數,或是去除了除錯資訊的程式碼塊)。
"f"也可以是一個函式。這種情況下,此函式僅能返回"f"形參的名字。]]
function foo(a, b) -- 1, 2
local x -- 3
do local c = a - b end -- "c"的作用範圍只在"do-end"之間,所以不會在函式"foo"中被計數。
local a = 1 -- 4
while true do
local name, value = debug.getlocal(1, a) -- 這裡的"a"是上面"local a = 1"的"a"。
if not name then break end
print(name, value)
a = a + 1 -- 索引+1,下一個變數。
end
end
foo(10, 20)
--[[ result:
a 10
b 20
x nil
a 4
]]
print()
for i = 1, 4 do
print(debug.getlocal(foo, i)) -- 提供函式名字,只能列印其形參。
end
--[[ result:
a
b
nil
nil
]]
--[[ debug.setlocal([thread,] level, local, value)
與"debug.getlocal()"的功能相對,
將"value"賦給"thread"執行緒中棧級別為"level"處函式的索引為"local"的區域性變數。
"thread"預設為當前執行緒。"level"只能指定為棧級別,而不能指定為函式名稱。
關於索引以及異常返回值,參見"debug.getlocal"函式。
如果執行成功,函式返回區域性變數的名字。]]
function foo(a, b) -- 1, 2
local x -- 3
do local c = a - b end -- "c"的作用範圍只在"do-end"之間,所以不會在函式"foo"中被計數。
local a = 1 -- 4
print(debug.getlocal(1, 1)) -- a 10
debug.setlocal(1, 1, 50)
print(debug.getlocal(1, 1)) -- a 50
end
foo(10, 20)
Q:如何除錯”metatable”資訊?
A:
--[[ debug.getmetatable(value)
返回"value"的"metatable",若"value"沒有"metatable"則返回"nil"。
debug.setmetatable(value, table)
將"value"的"metatable"設定為"table"(可以為"nil"),函式返回"value"。]]
local t1 = {__index = function (table, key)
return "metatable 1"
end
}
local t2 = {__index = function (table, key)
return "metatable 2"
end
}
local t = {}
setmetatable(t, t1)
print(t1, debug.getmetatable(t)) --> table: 00802C50 table: 00802C50
debug.setmetatable(t, t2)
print(t2, debug.getmetatable(t)) --> table: 00802D60 table: 00802D60
Q:如何除錯”userdata”資訊?
A:
--[[ debug.getuservalue(u)
返回關聯在"u"上的Lua值。如果"u"不是"userdata",則返回"nil"。
debug.setuservalue(udata, value)
將"value"設定為"udata"的關聯值。"udata"必須是一個"full userdata"。]]
附加:
1、儘可能只在除錯過程中使用除錯庫中的函式。首先,庫中一些函式的效能並不卓越。其次,它打破了Lua語言中一些基本的規則,比如函式中定義的區域性變數無法在其外部被訪問。最後,你一定不希望在你的最終產品中見到它的身影,所以你可以使用,debug = nil
來剔除除錯庫,同時減少最終產品的大小。
2、debug.getinfo()
對於”Tail Calls”,只將被包裹函式計入棧級別的計算,包裹函式不計入,
function g()
local x = debug.getinfo(1) -- 這裡獲取的是函式"g"的資訊。函式"f"不計入棧級別的計算。
for k, v in pairs(x) do
print(k, v)
end
end
function f()
return g()
end
f()
所以要檢視”Tail Calls”的包裹函式資訊,請直接指定函式名。
Q:如何除錯”Closure”的”upvalue”資訊?
A:
--[[ debug.getupvalue(f, up)
返回函式("Closure")"f"的第"up"個"upvalue"的名字和值。
Lua按照"upvalues"在匿名函式中出現的順序對其編號。如果指定的"up"索引越界,則返回"nil"。
以'('開頭的變數名錶示沒有名字的變數(比如是迴圈控制用到的控制變數,或是去除了除錯資訊的程式碼塊)。
debug.setupvalue(f, up, value)
與"debug.setupvalue()"的功能相對,將函式"f"("Closure")的第"up"個"upvalue"的值設定為"value"。
函式返回被設定的"upvalue"的名字。如果指定的"up"索引越界,則返回"nil"。
注:獲取與設定"upvalue"與"Closure"是否被呼叫(是否在呼叫棧上)無關。]]
-- "Closure"。
function newCounter ()
local n = 0
local k = 0
return function ()
k = n
n = n + 1
return n
end
end
counter = newCounter()
print(counter())
print(counter())
-- 此時"k"是1,"n"是2。
local i = 1
repeat
name, val = debug.getupvalue(counter, i)
if name then
print ("index", i, name, "=", val) -- 依次輸出兩個"upvalues"的名字和值。
if(name == "n") then
debug.setupvalue (counter, 2, 10) -- 設定"n"的值為10。
end
i = i + 1
end
until not name
-- 此時"n"的值被設定為10。
print(counter())
-- 在此呼叫後"n"的值被加1,變為11。
--[[ results:
1
2
index 1 k = 1
index 2 n = 2
11
]]
--[[ debug.upvaluejoin(f1, n1, f2, n2)
讓"Closure""f1"的第"n1"個"upvalue"引用"Closure""f2"的第"n2"個"upvalue"。
debug.upvalueid(f, n)
返回指定"Closure""f"的第"n"個"upvalue"的識別符號
(一個輕量使用者資料,每個"upvalue"的識別符號唯一)。
這個識別符號可以讓程式檢查兩個不同的"Closure"是否共享了相同的"upvalue(s)"。 ]]
function newCounter()
local n = 0
local k = 0
return function ()
k = n
n = n + 1
return n
end
end
counter = newCounter()
function newCounter1()
local n = 0
local k = 0
return function ()
k = n
n = n + 1
return n
end
end
counter1 = newCounter1()
-- 每個"upvalue"都有自己獨有的ID。
print(debug.upvalueid(counter, 1)) --> userdata: 00559300
print(debug.upvalueid(counter, 2)) --> userdata: 00559348
print(debug.upvalueid(counter1, 1)) --> userdata: 005593D8
print(debug.upvalueid(counter1, 2)) --> userdata: 00559420
-- 讓"counter"的第一個"upvalue"引用"counter1"的第二個"upvalue"。
debug.upvaluejoin(counter, 1, counter1, 2)
-- "counter"的第一個"upvalue"與"counter1"的第二個"upvalue"的ID相同。
print(debug.upvalueid(counter, 1)) --> userdata: 00559420
print(debug.upvalueid(counter, 2)) --> userdata: 00559348
print(debug.upvalueid(counter1, 1)) --> userdata: 005593D8
print(debug.upvalueid(counter1, 2)) --> userdata: 00559420
Q:如何追蹤程式的執行?
A:
--[[ debug.sethook([thread,] hook, mask [, count])
將函式"hook"設定為執行緒"thread"的鉤子函式。
"mask"決定鉤子函式何時被觸發,"count"決定何時額外的呼叫一次鉤子函式。
"thread"預設為當前執行緒。"count"預設為0,
鉤子函式將在每執行"count"條指令時額外的呼叫一次鉤子函式,向鉤子函式傳遞事件"count"。
"mask"可以指定為如下值的一個或多個:
'c': 每當Lua呼叫一個函式時,呼叫鉤子函式,向鉤子函式傳遞事件"call"或"tail call";
'r': 每當Lua從一個函式內返回時,呼叫鉤子函式,向鉤子函式傳遞事件"return";
'l': 每當Lua進入新的一行時,呼叫鉤子函式,向鉤子函式傳遞事件"line"。
當鉤子函式被呼叫時,第一個引數是觸發這次呼叫的事件。對於"line"事件,有第二個引數,為當前行號。
函式不傳參,則為關閉鉤子函式。]]
debug.sethook(print, "crl")
function foo()
local a = 1
end
local x = 1
foo()
local y = 1
--[[ results:
return nil
line 5
line 3
line 7
line 8
call nil
line 4
line 5
return nil
line 9
return nil
return nil
]]
--[[ debug.gethook([thread])
返回鉤子函式的記憶體地址,鉤子函式的掩碼,"debug.sethook()"為鉤子函式設定的"count"。]]
debug.sethook(print, "l", 9)
print(debug.gethook())
debug.sethook() -- 關閉鉤子函式。
print(debug.gethook()) -- 沒有鉤子函式就什麼都獲取不到了。
--[[ results:
line 2
function: 013D1A70 l 9
line 3
nil 0
]]
Q:如何檢視Lua的登錄檔資訊?
A:
--[[ debug.getregistry()
函式返回Lua的"registry"。]]
Q:如何建立一個程式分析器?
A:調式庫除了用於調式以外還可以用於完成其他任務,這種常見的任務就是分析。對於一個實時的分析來說,最好使用C介面來完成。對於每一個鉤子函式其使用的Lua呼叫代價太大,並且通常會導致測量的結果不準確。然而,對於計數分析來說,Lua可以很好的勝任。
-- 一個記錄程式中函式被呼叫次數的小型基本分析器。
local Counters = {} -- key-value: 函式-計數
local Names = {} -- key-value:函式-函式名
local function hook()
local f = debug.getinfo(2, "f").func -- 獲取被呼叫的函式本身。
if Counters[f] == nil then -- 如果是第一次被呼叫。
Counters[f] = 1
Names[f] = debug.getinfo(2, "Sn") -- 獲取函式資訊。
else -- 如果之前被記錄過,這裡只是增加其計數。
Counters[f] = Counters[f] + 1
end
end
local f = assert(load("print('Hello World!')"))
debug.sethook(hook, "c") -- 當函式被呼叫時呼叫鉤子函式。
f()
debug.sethook() -- 關閉鉤子函式。
-- 獲取結果。
function getname(func)
local n = Names[func]
if n.what == "C" then -- 如果是C函式,只返回其名字。
return n.name
end
-- 如果不是C函式,返回"[file]:line"的形式。
local loc = string.format("[%s]:%s", n.short_src, n.linedefined)
if n.namewhat ~= "" then -- 如果不是匿名函式,返回一個合理的名字,"[file]:line (name)"。
return string.format("%s (%s)", loc, n.name)
else -- 否則只返回"[file]:line"的形式。
return string.format("%s", loc)
end
end
for func, count in pairs(Counters) do
print(getname(func), count)
end
--[[ results:
Hello World!
[[string "print('Hello World!')"]]:0 (f) 1
print 1
sethook 1
nil 1 <-- 這個不知道是什麼函式。
]]
附加:
1、在鉤子函式內,你可以呼叫”debug.getinfo()”,指定棧級別為2, 來獲得正在執行的函式的詳細資訊(”debug.getinfo()”的棧級別為0,鉤子函式的棧級別為1)。
2、一個列印檔名及行號的精緻的追蹤器,
function trace(event, line)
local s = debug.getinfo(2).short_src
print(s .. ":" .. line)
end
debug.sethook(trace, "l")