快速掌握Lua 5.3 —— 调试库

Posted VermillionTear

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了快速掌握Lua 5.3 —— 调试库相关的知识,希望对你有一定的参考价值。

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")

以上是关于快速掌握Lua 5.3 —— 调试库的主要内容,如果未能解决你的问题,请参考以下文章

快速掌握Lua 5.3 —— 字符串库

快速掌握Lua 5.3 —— I/O库

快速掌握Lua 5.3 —— 字符串库

快速掌握Lua 5.3 —— 编写提供给Lua使用的C库函数的技巧

快速掌握Lua 5.3 —— 编写提供给Lua使用的C库函数的技巧

快速掌握Lua 5.3 —— userdata