OpenResty XRay™ Ylua 用户手册
ylua - 用于编写 Lua 应用追踪工具的类 Lua 语言编译器。
目录
概要
语言语法示例
probe foo(a, b)
print("a = ", a)
print("b = ", b)
end
probe bar()
print("k = ", k) -- k is an upvalue in the target for function bar.
end
语言特性
支持的 Lua 语法
支持以下 Lua 运算符和语法:
- 一元运算符
# - 一元运算符
not - 一元运算符
- - 二元运算符
+、-、*、/、%、.. - 二元运算符
>、>=、<、<=、==、!= - 二元运算符
and、or - 后缀
[KEY] - 后缀
.KEY - 括号(
()) - 可变参数(
...) - 布尔值(
true、false) if/elseif/else语句return语句do语句for v = fr, to, step do循环语句for i, e in ipairs(tb)循环语句for k, v in pairs(tb)循环语句while语句repeat ... until语句break语句local变量声明(支持多个变量和初始化表达式)- 赋值语句(支持多重赋值)
nil值- 函数调用语法
foo(...)、foo"..."、foo{...}等
支持的内置函数
Lua 内置函数
支持以下 Lua 内置函数:
requireprinttypeselecterrortostringtonumberipairs(仅作为for ... in循环中的表达式列表)pairs(仅作为for ... in循环中的表达式列表)table.maxn
额外的内置函数
支持以下额外的标准函数:
dump转储 Lua 值参数的详细信息。
out类似于
print(),但不会自动输出尾随的换行符。identity直接返回参数值。
exit退出探测会话(即整个追踪工具)。
warn向 stderr 发出警告消息。
contains如果第一个字符串参数包含第二个字符串参数,则返回
true,否则返回false。upval语法:
upval(name)语法:
upval(func, name)返回由
name参数指定名称的上值。关联的函数可以是当前正在探测的函数,也可以是由func参数显式指定的函数。只有 Lua 函数才有命名的上值。此upval()函数也可以在 Lua 函数探针说明符中使用,如下所示:probe (upval(require "foo".blah, "uvname")) (a, b) ... end
探针
Lua 函数探针
Lua 函数入口探针
支持在 Lua 函数入口点上进行动态探测。然而,某些直接用手工编写的汇编代码实现的 Lua 内置函数可能会错过探针,例如 math 命名空间下的一些 API 函数。
以下是一个示例:
probe foo(a)
print("arg a = ", a)
end
每次进入该函数(由于函数调用)时,它都会打印出目标中全局 Lua 函数 foo 的实际参数 a 的值。
当目标进程中实际 Lua 函数参数的数量可能变化时,也支持可变参数语法。例如:
probe foo(...)
for i = 1, select('#', ...) do
print(i, ": ", select(i, ...))
end
end
此探针处理程序将输出目标中全局 Lua 函数 foo 每次调用的所有实际参数。一个典型的输出可能如下所示:
1: 3
2: hello
3: 3.140000
如果目标 Lua 函数可能返回复合 Lua 值(如 Lua 表),我们应该在此示例中使用 dump() 内置函数,如下所示:
probe foo(...)
for i = 1, select('#', ...) do
print(i, ": ", dump(select(i, ...)))
end
end
一个示例输出如下:
1: 171
2: "hello"
3: true
4: table (GCtab*)0x7f96f754d830 (narr=0, nrec=1):
key:
"dogs"
value:
-3.140000
注意 dump() 的输出与直接打印 Lua 目标值的区别。
对于 Lua 函数入口探针,可以引用参数变量、上值变量和全局变量。非参数的局部变量不能在此类探针处理程序中引用,因为它们在该点自然尚未初始化。以下是一个示例:
probe foo(a)
print(a + b)
end
编译此 ylua 代码示例将产生以下警告:
WARNING: bar: symbol 'b' is assumed to be an upvalue or a global variable in the target Lua process at test.ylua line 2
显然,引用的变量 b 未声明,将被假定为目标中函数 foo 的上值,或者在探针触发时当前 Lua 线程的全局 Lua 变量。出于显而易见的原因,当前 Lua 函数的上值优先于全局变量查找。
任何 Lua 主表达式都可以用作探针说明符,如下所示:
probe (package.loaded["io"].open)(file_name, mode)
print("opening file ", file_name, " with mode ", mode)
end
甚至可以使用追踪器空间的内置函数调用,如下所示:
probe (require "io".open)(file_name, mode)
print("opening file ", file_name, " with mode ", mode)
end
注意,必须使用括号括起表达式,否则会存在语法歧义。
Lua 函数返回探针
支持在 Lua 函数返回点上进行动态探测。然而,由于尾调用固有的"goto"性质,可能会错过我们的探针。
考虑以下示例:
probe foo -> ()
print("function foo returning!")
end
每次目标中的全局 Lua 函数 foo() 返回时,此探针处理程序都会打印出 function foo returning! 这一行。这里我们不关心该函数退出时是否返回任何值。但当我们关心时,可以像这样检查返回值:
probe foo -> (a, b)
print("returning ", a, " and ", b)
end
此探针将输出目标 Lua 函数每次返回的前 2 个返回值。
与 Lua 函数入口探针类似,返回探针也支持 Lua 可变参数语法(...),以便在返回值数量不固定或事先未知时检查所有返回值。以下是这样一个示例:
probe foo -> (...)
for i = 1, select('#', ...) do
print(i, ": ", select(i, ...))
end
end
一个典型的输出如下:
1: 171
2: hello
3: true
4: -3.140000
如果返回值可能是复合 Lua 值(如 Lua 表),我们应该使用 dump() 内置函数,如下所示:
probe foo -> (...)
for i = 1, select('#', ...) do
print(i, ": ", dump(select(i, ...)))
end
end
对于 Lua 函数返回探针,可以访问对应 Lua 函数返回点可见的所有局部变量、上值和全局变量。请注意,如果返回点实际上不引用某些局部变量,Lua VM 可能会优化掉这些局部变量。
任何 Lua 主表达式都可以用作探针说明符,如下所示:
probe (package.loaded["io"].open) -> (file_handle, err)
if file_handle ~= nil then
print("opened file as handle ", file_handle)
else
print("failed to open file: ", err)
end
end
甚至可以使用追踪器空间的内置函数调用,如下所示:
probe (require "io".open) -> (file_handle, err)
if file_handle ~= nil then
print("opened file as handle ", file_handle)
else
print("failed to open file: ", err)
end
end
注意,必须使用括号括起表达式,否则会存在语法歧义。
C 函数探针
C 函数入口探针
要在 C 函数入口点上进行探测,我们可以编写如下内容:
probe C:lj_cf_collectgarbage()
print("foo: ", package.loaded.foo)
end
这里我们在 C 函数 lj_cf_collectgarbage 的入口点上进行探测,然后打印出 Lua 表达式 package.loaded.foo 的值。
标准探针
begin
与 ylang 的 _begin 探针相同。
end
与 ylang 的 _end 探针相同。
process.begin
与 ylang 的 _process.begin 探针相同。
timer.profile
与 ylang 的 _timer.profile 探针相同。
timer.s(N)
与 ylang 的 _timer.s(N) 探针相同。
timer.ms(N)
与 ylang 的 _timer.ms(N) 探针相同。
类型系统
ylua 语言具有以下值类型:
tv:用于存储目标进程中的 Lua 值(或"TValue"指针)。num:用于在追踪器空间中存储双精度数字。int:用于在追踪器空间中存储 32 位有符号整数。bool:用于在追踪器空间中存储布尔值true和false。str:用于在追踪器空间中存储字符串。nil:用于追踪器空间中的nil值(或void)。func:用于追踪空间中的内置或用户定义的 ylua 函数。
追踪器空间中的 ylua 变量可以采用上述所有数据类型,但 nil 类型除外。当 ylua 变量用 nil 初始化时,它是 tv 类型。tv 类型的变量也可以由 nil 类型的表达式赋值。
当函数不返回值时(如内置函数 print()),它的返回值类型为 nil。
每个 ylua 变量只能采用一种数据类型,其类型必须在编译时确定,并且在运行时不得更改。
所有用于存储目标进程空间中值的 ylua 变量必须是 tv 类型。自然地,来自目标进程的所有值都采用 tv 类型。
嵌入 ylang 源代码片段
可以使用以下语法嵌入任意 ylang 源代码片段:
ylang [=[
_probe _begin {
use_ngx_stream_lua_module = true;
_warn("Start tracing...\n");
}
]=]
基本上,嵌入的 ylang 代码被放入 Lua 字符串字面量中(为了省去转义的麻烦,这里首选长括号字符串字面量)。
除了使用 ylang 嵌入顶层 ylang 代码片段外,还支持在任何其他上下文中嵌入 ylang 代码,例如在 Lua 函数入口探针处理程序中:
probe foo()
print("foo called!")
ylang [=[
printf("from ylang...\n");
]=]
end
待办事项
- 使
for k, v in pairs(tb)循环也遍历表的数组部分。 - 运算符
%应该直接作用于浮点数。 and和or运算符表达式不应该只返回布尔值。- 支持从类数字字符串值转换为数字。
- Lua 的
string.format内置函数。 - Lua 的
string.find内置函数。 - Lua 的
string.byte内置函数。 - Lua 的
string.char内置函数。 - Lua 的
string.sub内置函数。 - Lua 的
table.concat内置函数。 - Lua 的
collectgarbage("count")内置函数。 - Lua 的
math.*API 函数。 - Lua 的
coroutine.status()API 函数。 - Lua 的
getmetatable()API 函数。 - Lua 的
getfenv()API 函数。 - LuaJIT 的
bit.*API 函数。 - LuaJIT 的
ffiAPI,用于操作 cdata 的 C 数据结构和 C 类型,如cdata.field[index]。 - 新的
table.narr()内置函数。 - 新的
table.nrec()内置函数。 - 新的
dump_bt()和dump_full_bt()内置函数,用于转储 Lua 回溯。也添加对debug.traceback()的支持,它只是dump_bt()的别名。 - 新的
agg数据类型用于聚合变量,<<<运算符,以及相应的统计内置函数,如agg.count()、agg.avg()、agg.max()、agg.min()、agg.sum()、agg.hist_log()、agg.hist_linear()等。 - 支持在追踪器空间中定义用户函数(就像定义新的 Lua 函数一样)。
- 支持在 cdata 函数上进行探测,如
ffi.C.foo()。 - 新的
_Continue关键字和语句,类似于 C 的循环continue语句。 - 添加 ylang 风格的内置哈希表变量和数组变量。
- 添加内置探针,如
begin、end、syscall.*等。
作者
Yichun Zhang (agentzh) yichun@openresty.com
版权与许可
版权所有 (C) 2018-2025 OpenResty Inc. 保留所有权利。
本软件为专有软件,不得以任何方式重新分发或共享。
另请参阅
- Ylang 编译器。