• Lua 调试库


    Lua 调试库

    http://blog.csdn.net/vermilliontear/article/details/50851045

    http://blog.csdn.net/vermilliontear/article/details/50865156

    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")
  • 相关阅读:
    2020 CCPC-Wannafly Winter Camp Day6 ---I. 变大!
    Codeforces 1295F Good Contest
    2020 CCPC-Wannafly Winter Camp Day6 ---A. Convolution
    centos下kubernetes+flannel部署(旧)
    无网络centos7中部署kubernetes
    利用Openvswitch实现不同物理机中的Docker容器互连
    docker-py的配置与使用
    通过Docker配置DNS服务器
    在 OS X Yosemite 中部署Mesos
    Docker初识
  • 原文地址:https://www.cnblogs.com/lsgxeva/p/7785211.html
Copyright © 2020-2023  润新知