Lua反射

本文详细介绍了Lua的自省机制,包括getinfo函数的使用,展示了如何访问局部和非局部变量,以及如何利用钩子进行调试和性能调优。此外,还探讨了Lua的沙盒实现,通过限制指令执行数量和内存使用,以及控制可调用的函数,以防止DoS攻击。
摘要由CSDN通过智能技术生成

文章新地址


  反射是程序用来检查和修改其自身某些部分的能力。像Lua语言这样的动态语言支持几种反射机制:环境允许运行时观察全局变量;诸如type和pairs这样的函数允许运行时检查和遍历未知数据结构;诸如load和require这样的函数允许程序在自身中追加代码或更新代码。不过,还有很多方面仍然是缺失的:程序不能检查局部变量,开发人员不能跟踪代码的执行,函数也不知道是被谁调用的,等等。调试库填补了上述缺陷。
  调试库是由两类函数组成:自省函数和钩子。自省函数允许我们检查一个正在运行中的程序的几个方面,例如活动函数的额栈、当前正在执行的代码行、局部变量的名称和值。钩子则允许我们跟踪一个程序的执行。
    虽然名字里带有”调试“的字眼,但调试库提供的并不是Lua语言的调试器。不过,调试库提供了编写我们自己的调试器所需要的不同层次的所有底层机制。
  调试库与其他库不同,必须被慎重地使用。首先,调试库中的某些功能的性能不高。其次,调试库会打破语言的一些固有规则,例如不能从一个局部变量的词法定界范围外访问这个局部变量。虽然调试库作为标准库直接可用,但笔者建议在使用调试库的代码段中显示地加载调试库。

自省机制

  调试库中主要的自省函数是getinfo,该函数的第一个参数可以是一个函数或一个栈层次。当为某个函数foo调用debug.getinfo(foo)时,该函数会返回一个包含与该函数有关的一些数据的表。这个表可能具有以下字段:
   source: 该字段用于说明函数定义的位置。如果函数定义在一个字符串中(通过调用load),那么source就是这个字符串;如果函数定义在一个文件中,那么source就是使用@作为前缀的文件名。
   short_src: 该字段是source的精简版本,对于错误信息十分有用。
   linedefined: 该字段是该函数定义在源代码中第一行的行号。
   lastlinedefined: 该字段是该函数定义在源代码中最后一行的行号。
   what: 该字段用于说明函数的类型。如果foo是一个普通的Lua函数,则为“Lua”;如果是一个C函数,则为“C”;如果是一个Lua语言代码段的主要部分,则为“main”。
   name: 该字段是该函数的一个适当的名称,例如保存该函数的全局变量的名称。
   namewhat: 该字段用于说明上一个子弹的含义,可能是"global"、“local”、“method”、“filed"或”"(空字符串)。空字符串表示Lua原因找不到该函数的名称。
   nups: 该字段是该函数的上值的个数。
   nparams: 该字段是该函数的参数个数。
   isvararg: 该字段表明该函数是否为可变长参数函数。
   activelines: 该字段是一个包含该函数所有活跃行的集合。活跃行时指除空行和只包含注释的外行的其他行。
   func: 该字段是该函数本身。
   当foo是一个C函数时,Lua语言没有多少关于该函数的信息。对于这种函数,只有字段what、name、namewhat、nups和func是有意义的。
    当使用给一个数字n作为参数调用函数debug.getinfo(n)时,可以得到有关相应栈层次上活跃函数的数据。栈层次是一个数字,代表某个时刻上活跃的特定函数。调用getinfo的函数A的层次是1,而调用A的函数的层次是2,以此类推。如果n大于栈中活跃函数的数量,那么函数debug.getinfo返回nil。当通过带有栈层次的debug.getinfo查询一个活跃函数时,返回的表中海油两个额外字段:currentline,表示当前该函数正在执行的代码所在的行;istailcall,如果为真则表示函数是被尾调用所调起。
    字段name有些特殊。请注意,由于函数在Lua语言中是第一类值,因此函数既可以没有名称也可以有多个名称。Lua语言会通过检查调用该函数的代码来看函数是如何被调用的,进而尝试找到该函数的名称。这种方法只有在以一个数字为参数调用getinfo时才会起作用,即我们只能获取关于某一具体调用的信息。
    函数getinfo的效率不高。Lua语言以一种不影响程序执行的形式来保存调试信息,至于获取这些调试信息的效率则是次要的。为了实现更好的性能,函数getinfo有一个可选的第二参数,该参数用于指定希望获取哪些信息。通过这个参数,函数getinfo就不会浪费时间去收集用户不需要的数据。这个参数是一个字符串,其中每个字母代表选择一组字段,如下表所示:


n            选择name和namewhat
f            选择func
S            选择source、short_src、what、linedefined和lastlinedefined
l            选择currentline
L            选择activelines
u            选择nup、nparams和isvararg


    下面这个函数演示了函数debug.getinfo的用法,它打印出了活跃栈的栈回溯:

funciton traceback()
	for level = 1, math.huge do
		local info = debug.getinfo(level,"Sl")
		if not info then back end
		if info.what == "C" then
			print(string.format("%d\tC function",level))
		else
			print(string.format("%d\t[%s]:%d",level,
				info.short_src, info.currentline))
		end
	end
end

要改进这个函数并不难,只需要让函数getinfo返回更多数据即可。实事上,调试库也提供了这样一个改进版本,即函数traceback。与我们的版本不同的是,函数debug.traceback不会打印结果,而是返回一个包含栈回溯的字符串:

> print(debug.traceback())
stack traceback:
	stdin:1:in main chunk
	[C]:in ?

访问局部变量

    我们可以通过函数debug.getlocal来检查任意活跃函数的局部变量。该函数有两个参数,一个是要查询函数的栈层次,另一个是变量的索引。该函数返回两个值,变量名和变量的当前值。如果变量索引大于活跃变量的数量,那么函数getlocal返回nil。如果栈层次无效,则会抛出异常。
    Lua语言按局部变量在函数中的出现顺序对它们进行编号,但编号只限于在函数当前作用域中活跃的变量。例如,考虑如下的代码:

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

调用fo

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

平淡风云

您的打赏是我继续创作的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值