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

Lua 调试库的更多相关文章

  1. Lua标准库- 模块(Modules)

    Lua包库为lua提供简易的加载及创建模块的方法,由require.module方法及package表组成 1.module (name [, ···]) 功能:建立一个模块. module的处理流程 ...

  2. Lua标准库(转)

    转载地址:http://www.yiibai.com/lua/lua_standard_libraries.html Lua的标准库提供了一组丰富的功能,与C的API直接实现,建立在Lua编程语言函数 ...

  3. 【Tip】如何让引用的dll随附的xml注释文档、pdb调试库等文件不出现在项目输出目录中

    项目输出目录(bin/debug|release)中经常是这个样子: main.exemain.pdb a.dll a.xml b.dll b.pdb b.xml ... 其中xml是同名dll的注释 ...

  4. Lua字符串库(整理)

    Lua字符串库小集 1. 基础字符串函数:    字符串库中有一些函数非常简单,如:    1). string.len(s) 返回字符串s的长度:    2). string.rep(s,n) 返回 ...

  5. lua工具库penlight--01简介

    lua的设计目标是嵌入式语言,所以和其它动态语言(如python.ruby)相比其自带的库缺少很多实用功能. 好在有lua社区有Penlight,为lua提供了许多强大的功能,接下来的几篇博客,我会简 ...

  6. lua调试的工具选择

    近期看到一个关于vs的lua调试插件, 装了vs2012试了下, 忍不住发此文总结下lua各种调试工具 Decoda 这是现今地球上调试lua5.1最方便的工具, 没有之中的一个. 强大的注入式调试, ...

  7. lua math 库

    lua math库 (2012-05-18 17:26:28) 转载▼ 标签: 游戏 分类: Lua atan2.sinh.cosh.tanh这4个应该用不到. 函数名 描述 示例 结果 pi 圆周率 ...

  8. Lua 学习之基础篇五<Lua OS 库>

    lua os库提供了简单的跟操作系统有关的功能 1.os.clock() 返回程序所运行使用的时间 local nowTime = os.clock() print("now time is ...

  9. 2019-2-14-VisualStudio-通过外部调试方法快速调试库代码

    title author date CreateTime categories VisualStudio 通过外部调试方法快速调试库代码 lindexi 2019-2-14 22:1:37 +0800 ...

随机推荐

  1. Drupal错误:drupal Maximum execution time of 30 seconds exceeded database in解决方法

    Drupal开源内容管理框架 Drupal是使用PHP语言编写的开源内容管理框架(CMF),它由内容管理系统(CMS)和PHP开发框架(Framework)共同构成.连续多年荣获全球最佳CMS大奖,是 ...

  2. 利尔达推出工控解决方式 串口转以太网模块LSD1ES-W5500_S2E0

    利尔达最近推出工控解决方式,串口转以太网模块LSD1ES-W5500_S2E0,模块基于WIZnet-W5500. 同一时候,这也是利尔达科技集团成为WIZnet代理商后,自行推出的第一款基于WIZn ...

  3. PuTTY+Xming实现X11的ssh转发

    1 需求分析 有些Linux程序还是不能完全离开窗口环境,或者说离开后操作不方便.其中Oracle就是这样一个程序,其工具程序大多数能够在纯命令行静默执行,如 OCI,DBCA,NetCA等,但是工作 ...

  4. Scala _ 下划线

    1.引入包中的全部方法 import math._ //引入包中所有方法,与java中的*类似 2.表示集合元素 val a = (1 to 10).filter(_%2==0).map(_*2) / ...

  5. 2017-2018-2 20155203《网络对抗技术》Exp4 恶意代码分析

    1. 实践过程记录 1. 使用Windows计划任务schtasks监控系统运行 Windows计划任务schtasks监控系统: 在C盘建立一个netstatlog.bat文件,用来将记录的联网结果 ...

  6. 20155333 《网络对抗》 Exp9 Web安全基础

    20155333 <网络对抗> Exp9 Web安全基础 基础问题回答 1.SQL注入攻击原理,如何防御? 原理: 通过在用户名.密码登输入框中输入一些',--,#等特殊字符,实现引号闭合 ...

  7. JavaScript快速入门-简介

    一.JavaScript历史(摘自w3school) JavaScript 是因特网上最流行的脚本语言,它存在于全世界所有 Web 浏览器中,能够增强用户与 Web 站点和 Web 应用程序之间的交互 ...

  8. android studio 下载 sdk 失败

    android studio 打开项目出现以下错误时,点击去安装,会提示"All packages are not available for download" 错误. 解决办法 ...

  9. wordpress 5.1 后台拿shell

    闲着没事搭建了一套最新版wordpress 5.1,本身wordpress后台安全防御是比较差的,想尝试下后台是否可以拿shell. 再上传插件的地方可以直接上传php文件,并且该文件可以执行: sh ...

  10. Linux内核分析 笔记四 系统调用的三个层次 ——by王玥

    一.知识点总结 (一)用户态.内核态和中断 1.内核态:在高的执行级别下,代码可以执行特权指令,访问任意的物理地址,这时的CPU就对应内核态 2.用户态:在低级别的指令状态下,代码 只能在级别允许的特 ...