1. 程式人生 > >Lua 除錯庫

Lua 除錯庫

轉載: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")