Programming in Lua, 2Nd Edition - Chapter 23: The Debug Library

 

 

 

 

Chapter 23: The Debug Library

 

Debug 库没有给你一个lua 调试器,但是当你要写自已的调试器时,它提供了所有你需要的东西。因为效率的原因,官方接口是通过C API提供。Lua 中的调试库是一种在lua 代码中直接访问它们方法。不像其它的库,你应该吝啬的使用调试库。首先,是性能的原因。其次,它打破了语言的一些“哲学”,例如你不能从函数的外部访问一个由那个函数创建的局部变量。通常,你可能不希望在你的最终产品中打开这个库,或你可能想要擦除它,运行debug=nil

 

 

调试库由两种函数组成:introspective functions hooks

 

Introspective 函数可以检查运行中的程序的许多信息,如活动函数的栈,当前执行行,局部变量的名字和值Hooks 函数能够跟踪程序的执行。

 

调试库的一个重要概念是栈层级(stack level)。栈层级是一个与特殊函数相关联的数字,这个特殊函数是活动函数,所谓的活动函数就是已经被调用,并且还没有返回的函数。调用debug 库的那个函数的栈层级是1,调用“那个函数”的函数的栈层级是2,等等。

 


 

23.1 Introspective Facilities

 

debug.getinfo 是主要的introspective function 函数。它的第一个参数可以是一个函数或一个栈层级。当你调用debug.getinfo(foo),你获得一个有关这个函数的一些数据的表。这个表可以有以下域:

 

source:函数在哪里被定义。如果函数是在一个string 中定义(通过loadstring),source 是这个string。如果函数是在一个文件中定义,source 是这个文件的文件名,带有前缀“@”。

 

short_src: 一段被缩短了的源代码(最多60 字符),对错误消息有用。

 

linedefined: 函数是在哪一行开始定义。

 

lastlinedefined: 函数是在哪一行结束定义。

 

what: 这个函数是什么。对普通lua 函数是“lua”,C 函数是“C”,lua main chunk是“main”。

 

name: 有意义的函数名。

 

namewhat: name 域的解释。可能值是“global”,“local”,“method”,“field”或“”(空串)。空串意味着lua 找不到以那个名字命名的函数。

 

nups: 函数的upvalues(更新了多少个值?) 数。

 

activelines: 一个表,描述了函数的有效行(active line)的集。active line 是一行代码,不同于空行或只包含注释的行。(典型用法是用此信息来设置断点。多数调试器不允许在无效行上设置断点,因为它不可达)

 

func: 函数自身。

 

当传给debug.getinfo 的参数foo 是一个C 函数,lua 只会返回这个函数的少量信息。只有whatname,和namewhat 有效

 

调用debug.getinfon),可以获得在第n 栈层级上的活动函数的数据。例如,如果n 1,你会获得关于执行call 的那个函数的数据。(当n 0,你得到的数据是关于getinfo 这个C 函数本身)。如果n 大于栈中的活动函数的个数,debug.getinfo 返回nil。当你调用debug.getinfo(n) 查询一个活动函数,返回的结果表有一个额外域,既当前行currentline,表示函数此刻运行到这一行了。此外,func 拥有在那个栈层级上活动的函数。

 

name 域是tricky。记得,因为lua 中函数是第一类型,一个函数可能没有名字,或可能有多个名字。Lua 试着去为函数找一个名字,进入被调用的函数的代码中,看看它是如何被调用的。这个方法仅当我们调用getinfo(n) 有效,就是说,我们获得有关一个特别invocation 的信息。

 

getinfo 函数是效率不高的。lua 将调试信息保存在一个form 中,它不impair 程序的执行。 在这里提高效率不是首要目标,而是第二目标。为获得更好的性能,getinfo 有一个可选的第二个参数,这个参数用来选择欲获取的信息。使用这个参数,getinfo 函数不会浪费时间收集那些用户不需要的数据此参数是一个string,里面的每一个字母选择一组fields,对应于下表:

 

`n' selects name and namewhat

`f' selects func

`S' selects source, short_src, what, linedefined, and lastlinedefined

`l' selects currentline

`L' selects activelines

`u' selects nup

 


 

打印active stack traceback

 

function traceback ()

       for level = 0, math.huge do

              local info = debug.getinfo(level, "Sl")

              if not info then break end

              if info.what == "C" then -- is a C function?

                     print('level:'..level, "C function")

              else -- a Lua function

                     print(string.format("level:%d [%s]:%d", level,info.short_src,

                     info.currentline))

              end

       end

end

traceback()

-- n 0,你得到的数据是关于getinfo 这个C 函数本身

-- n 1,你得到的数据是关于调用getinfo 这个函数的函数

 

要改进这个程序不难,通过从getinfo 插入更多数据。实际上,debug 库就有这么个改进版本,就是traceback 函数。和我们的版本不同,debug.traceback 不打印其结果,而是返回一个traceback string(通常很长)。

 


 

Accessing local variables

 

我们可以通过debug.getlocal 来检视任何活动函数的局部变量。此函数有两个参数:欲查询函数的栈层级和变量索引。返回值是:这个变量的变量名和当前值。如果变量索引大于活动变量数,getlocal 返回nil。如果栈层级无效,则引发错误。我们可以使用debug.getinfo 来检查栈层级的有效性。

 

Lua 以局部变量在函数中出现的先后顺序用123 4 … 来对局部变量的索引进行编号,并且只统计那些在函数的当前范围活动的局部变量

 

局部变量的索引顺序

 

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, 20)

-->a 10

-->b 20

-->x nil

-->a 4

 

具有索引1 的变量是a(第一个参数),2 b3 x4 是另一个a。在函数getlocal 的调用点,c 已经超出范围。

 

你也可以改变局部变量的值,使用debug.setlocal。头两个参数分别是栈层级和变量索引。第三个参数是欲设置的新值。返回值是变量名或nil,如果变量索引超出范围。

 


 

Accessing non-local variables

 

debug 库也允许我们访问非局部变量,使用getupvalue 函数。和局部变量不同,由函数所引用的非局部变量一直存在,既使当函数不是活动的时也一样。所以,getupvalue 的第一个参数不是栈层级,而是一个函数(更准确的说是一个闭包)。第二个参数是变量索引。

 

Lua 以非局部变量在函数(既闭包)中被引用的先后顺序用123 4 … 来对非局部变量的索引进行编号,但这个顺序不是relevant 的,因为函数不能使用同一个名字访问两个非局部变量。

 

可以用debug.setupvalue 来更新非局部变量的值。像你所希望的那样,它有三个参数:一个闭包,一个变量索引,还有一个欲设置的新值。返回值是变量名,或nil,如果变量索引超出范围。

 

 

打印各栈层级上名字为“a”的变量

 

function getvarvalue (name)  -- level 1

       local value, found

       -- try local variables

       for i = 1, math.huge do

              local n, v = debug.getlocal(2, i)   -- level 0

              if not n then break end

              if n == name then

                     value = v

                     found = true

                     print(n, v)

              end

       end

       if found then return value end

       -- try non-local variables

       local func = debug.getinfo(2, "f").func

       for i = 1, math.huge do

              local n, v = debug.getupvalue(func, i)

              if not n then break end

              if n == name then return v end

       end

       -- not found; get from the environment

       return getfenv(func)[name]

end

 

function foo (a, b)        --  level 2

       local x

       do local c = a - b end

       local a = 1           -- 这个a 索引是2,实参a 的索引是1

       print(getvarvalue("a")) -- 打印具有最大索引的"a"  -->1

end

foo(10, 20)

 

给定一个变量名,我们如何从一个正被调用的函数中获得这个变量的值。首先试着找局部变量,如果存在多个同名局部变量,我们就返回那个具有最高索引的。所以我们必须走完整个循环。如果找不到具有那个名字的局部变量,那么就试着去找非局部变量。首先,我们使用debu.getinfo 获得calling function,然后遍历它的非局部变量。如果非局变量也找不到,就在全局变量中找。注意使用数字2 作为第一个参数,还有debug.getinfo 去访问calling function

 


 

Accessing other coroutines

 

debug 库的所有introspective 函数接受一个可选参数,既用协程作为第一个参数。所以,我们可以从外部检视协程。例如,考虑下面这个例子:

 

对协程进行tracebackgetlocal

 

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'

-->        a.lua:4: in function <a.lua:2>

 

print(coroutine.resume(co))

-->false   a.lua:5: some error

 

print(debug.traceback(co))

-->stack traceback:

-->          [C]: in function 'error'

-->          a.lua:5: in function <a.lua:2>

 

print(debug.getlocal(co, 1, 1))

-->x 10

 

trace 不会go through resume 的调用,因为协程和主程序运行于不同的栈。如果协程引发一个错误,它不会展开它的栈。这意味着,我们可以在错误发生后检视它。继续我们的例子,如果我们再一次resume 协程,它遭遇了错误:

 

print(coroutine.resume(co)) --> false temp:4: some error

 

 

现在,如果我们打印它的traceback,我们得到这样的信息:

print(debug.traceback(co))

-->stack traceback:

-->          [C]: in function 'error'

-->          a.lua:5: in function <a.lua:2>


 

我们也可以检视一个协程的局部变量的值,既使是在发生错误之后

 

print(debug.getlocal(co, 1, 1))

-->x 10

 

 

23.2 Hooks

 

debug 库的hook 机制允许我们注册一个函数(回调函数),程序运行过程中当一个特定事件发生时这个函数会被调用。有四种事件可以引发一个hookcall eventsreturn eventsline eventscount events。其中call events 是在每次lua 调用一个函数时发生,return events 是在一个函数返回时发生,line events 是在lua 开始执行一行新代码时发生,count events 是在程序的执行到达一个给定的指令数后发生。lua 以单个参数调用hooks,既一个string,可以是“call”,“return”,“line”,或“countline 事件,可以有第二个参数,既行号。要获得一个hook 内部的更多信息,我们必须使用debug.getinfo

 

要注册一个hook,我们以两个或三个参数调用debug.sethook:第一个参数是作为hook 的函数(回调函数);第二个参数是一个string 表示欲监视的事件;第三个是可选参数,一个数字,表示我们希望以多大的频率获得count 事件。为监视callreturnline 事件,我们在mask string中添加它们的第一个字母(c’,‘r’,或‘l)。为监视count 事件,我们简单偍供一个计数器作为第三个参数。要关闭hooks,我们以空参调用sethook

 

打印lua 解释器执行的每行代码的行号

 

debug.sethook(print, "l")

 

这个调用简单的安装print 函数作为hook 函数,lua  会在line events 事件发生时调用它。

 

一个更精细的追踪器是使用getinfo 来获得文件名:

 

function trace (event, line)

       local s = debug.getinfo(2).short_src

       print(s .. ":" .. line)

end

debug.sethook(trace, "l")

 

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'

-->        a.lua:4: in function <a.lua:2>

 

print(coroutine.resume(co))

-->false   a.lua:5: some error

 

print(debug.traceback(co))

-->stack traceback:

-->          [C]: in function 'error'

-->          a.lua:5: in function <a.lua:2>

 

print(debug.getlocal(co, 1, 1))

-->x 10

 


 

23.3 Profiles

 

debug 库也可以用来完成debugging 以外的任务。一个常见的任务是进行profiling。这一节,我们将开发一个简单的性能分析器,用来列出程序中每个被调用的函数的执行时间。

 

debug 库分析马尔可夫链算法的性能

 

-- input.txt

the more we try the more we do

---------------------

-- markov.lua

function allwords (f)

       local line = f:read("*line")    -- current line

       local pos = 1                           -- current position in the line

       return function ()

                            while line do

                                   local s, e = string.find(line, "%w+", pos)

                                   if s then -- found a word?

                                          pos = e + 1

                                          return string.sub(line, s, e)

                                   else

                                          line = f:read("*line")

                                          pos = 1

                                   end

                            end

                            return nil

                end

end

 

 

 

function prefix (w1, w2)

       return w1 .. " " .. w2

end

 

local statetab = {}

 

function insert (index, value)

       local list = statetab[index]

       if list == nil then

              statetab[index] = {value}

       else

              list[#list + 1] = value

       end

end

 

f = io.open("c://input.txt","r") -- open input file

assert(f)

 

local N = 2

local MAXGEN = 10000

local NOWORD = "/n"

 

-- build table

local w1, w2 = NOWORD, NOWORD

for w in allwords(f) do

       insert(prefix(w1, w2), w)

       w1 = w2; w2 = w;

end

insert(prefix(w1, w2), NOWORD)

 

-- generate text

w1 = NOWORD; w2 = NOWORD -- reinitialize

for i=1, MAXGEN do

       local list = statetab[prefix(w1, w2)]

 

       -- choose a random item from list

       local r = math.random(#list)   -- Bug,这里的随机数是不随机的。

       local nextword = list[r]

 

       if nextword == NOWORD then return end

       io.write(nextword, " ")

       w1 = w2; w2 = nextword

end

 

f:close()

-------------------------------

-- a.lua

 

local Counters = {}

local Names = {}

 

local function hook ()

       local f = debug.getinfo(2, "f").func

       if Counters[f] == nil then -- first time 'f' is called?

              Counters[f] = 1

              Names[f] = debug.getinfo(2, "Sn")

       else -- only increment the counter

              Counters[f] = Counters[f] + 1

       end

end

 

local f = assert(loadfile('markov.lua'))

debug.sethook(hook, "c") -- turn on the hook for calls

f() -- run the main program

debug.sethook() -- turn off the hook

 

function getname (func)

       local n = Names[func]

       if n.what == "C" then

              return n.name

       end

       local lc = string.format("[%s]:%s", n.short_src, n.linedefined)

       if n.namewhat ~= "" then

              return string.format("%s (%s)", lc, n.name)

       else

              return lc

       end

end

 

for func, count in pairs(Counters) do

       print(getname(func), count)

end

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值