OpenResty XRay™ Y 语言用户参考手册
OpenResty XRay Y 语言用户参考手册
目录
- 目录
- 语言参考
- 宏
##ifdefvar##ifndefvar##elifdefvar##elifndefvar##ifdefenum##ifndefenum##elifdefenum##elifndefenum##ifdeffunc##ifndeffunc##elifdeffunc##elifndeffunc##else##endif##error##ifdeffield##ifndeffield##elifdeffield##elifndeffield##ifdeftype##ifndeftype##elifdeftype##elifndeftype##ifdefarg##ifndefarg##elifdefarg##elifndefarg##yexe##yendexe##yexeonly##yendexeonly##nosym##reset
- 探针
- 用户定义的追踪器函数
- 内置类型
- 新语句
- 新运算符
- GCC 内置函数
- 内置函数
- _reg
- _pc_reg
- _sp_reg
- _tostr
- _tostr_quoted
- _contains
- _isprefix
- _issuffix
- _substr
- _strtol
- _warn
- _error
- _exit
- _push
- _pop
- _shift
- _unshift
- _elems
- printf
- sprintf
- assert
- _now_s
- _now_ms
- _now_us
- _now_ns
- _ktime_ns
- _uaddr
- _len
- _randint
- _log1
- _chop_token
- _max
- _variance
- _min
- _sum
- _avg
- _count
- _del_breakpoint
- _hist_log
- _ubt
- _ubt2
- _sym_ubt
- _usym
- _print
- puts
- fabs
- fabsf
- fmod
- fmodf
- remainder
- remainderf
- sqrt
- sqrtf
- _pid
- _tid
- _pgid
- _actions
- _arg_long
- _execname
- 宏
- 作者
- 版权与许可
语言参考
Y 语言(或 ylang)紧密遵循 C 语言语法。它努力与 C11 标准保持(相当程度的)兼容。
不过,一些标准 C 语言特性目前尚不支持:
带字段的 C
struct定义,例如:struct foo { int a; char *p; }目前我们只能声明不透明的 C
struct类型,如:struct foo;或
typedef struct foo foo;带成员字段的 C
union定义,例如:union foo { int a; char *p; }目前我们只能声明不透明的 C
union类型,如:union foo;或
typedef union foo foo;不支持像
stdint.h和stddef.h这样的标准 C 头文件, 不应通过#include指令将它们包含在 ylang 程序中。但是,您可以 自由地通过#include包含其他 ylang 文件。
值得注意的是,由于我们直接在用户 ylang 源代码上运行 gcc 的预处理器, 因此支持完整的 gcc 宏指令(包括可变参数宏!)。
Ylang 还有各种自己的语言扩展:
宏
支持所有标准 C 预处理器指令,包括 gcc 特定功能(如可变参数宏)。
在底层,ylang 编译器实际上调用 gcc 的预处理器。
此外,ylang 支持以下特殊宏指令。 除非特别指定,它们通常在标准 C 宏指令之前运行。
##ifdefvar
语法: ##ifdefvar VAR
这个宏是一个类似于 #if 的条件,当顶层(static 或全局)变量 VAR
在目标进程的调试信息中定义时(包括动态链接库),它的值为真。
此分支应以 ##endif(或 ##elifxxx)结束。
例如:
##ifdefvar ngx_cycle
printf("ngx_cycle = %p\n", &ngx_cycle);
##endif
##ifndefvar
语法: ##ifndefvar VAR
此宏条件类似于 ##ifdefvar,但仅在顶层变量 VAR 未在目标进程中定义时才为真。
例如:
##ifndefvar my_global_var
_error("Variable my_global_var is not defined in target process");
##endif
##elifdefvar
语法: ##elifdefvar VAR
此宏类似于 ##ifdefvar,但只能在 ##ifxxx 或 ##elifxxx 指令之后使用。
例如:
##ifdefvar my_global_var
_error("Variable my_global_var is defined in target process");
##elifdefvar ngx_cycle
_error("Variable ngx_cycle is not defined in target process");
##endif
##elifndefvar
语法: ##elifndefvar VAR
类似于 ##elifdefvar,但条件取反。
例如:
##ifndefvar my_global_var
_error("Variable my_global_var is not defined in target process");
##elifndefvar my_gloabl_var2
_error("Variable my_global_var2 is not defined in target process");
##endif
##ifdefenum
语法: ##ifdefenum ENUM
这个宏是一个类似于 #if 的条件,当顶层(static 或全局)枚举 ENUM
在目标进程的调试信息中定义时(包括动态链接库),它的值为真。
此分支应以 ##endif(或 ##elifxxx)结束。
例如:
##ifdefenum NGX_HTTP_OK
int status = NGX_HTTP_OK;
##endif
##ifndefenum
语法: ##ifndefenum ENUM
此宏条件类似于 ##ifdefenum,但仅在顶层枚举 ENUM 未在目标进程中定义时才为真。
例如:
##ifndefenum MY_CUSTOM_ENUM
_warn("Enum MY_CUSTOM_ENUM not found, using default value");
##endif
##elifdefenum
语法: ##elifdefenum ENUM
此宏类似于 ##ifdefenum,但只能在 ##ifxxx 或 ##elifxxx 指令之后使用。
例如:
##ifdefenum NGX_HTTP_OK
int status = NGX_HTTP_OK;
##elifdefenum NGX_HTTP_SUCCESS
int status = NGX_HTTP_SUCCESS;
##endif
##elifndefenum
语法: ##elifndefenum ENUM
类似于 ##elifdefenum,但条件取反。
例如:
##ifndefenum MY_CUSTOM_ENUM
_warn("Enum MY_CUSTOM_ENUM not found, using default value");
##elifndefenum MY_CUSTOM_ENUM2
_warn("Enum MY_CUSTOM_ENUM2 not found, using default value");
##endif
##ifdeffunc
语法: ##ifdeffunc FUNC
这个宏是一个类似于 #if 的条件,当函数 FUNC
在目标进程的调试信息中定义时(包括动态链接库),它的值为真。
此分支应以 ##endif(或 ##elifxxx)结束。
例如:
##ifdeffunc ngx_http_process_request
_probe ngx_http_process_request() {
printf("Processing HTTP request\n");
}
##endif
##ifndeffunc
语法: ##ifndeffunc FUNC
此宏条件类似于 ##ifdeffunc,但仅在函数 FUNC 未在目标进程中定义时才为真。
例如:
##ifndeffunc my_custom_function
_warn("Function my_custom_function not available");
##endif
##elifdeffunc
语法: ##elifdeffunc FUNC
此宏类似于 ##ifdeffunc,但只能在 ##ifxxx 或 ##elifxxx 指令之后使用。
例如:
##ifdeffunc ngx_http_process_request
_probe ngx_http_process_request() {
printf("Processing HTTP request\n");
}
##elifdeffunc ngx_http_close_request
_probe ngx_http_close_request() {
printf("close HTTP request\n");
}
##endif
##elifndeffunc
语法: ##elifndeffunc FUNC
类似于 ##elifdeffunc,但条件取反。
例如:
##ifndeffunc my_custom_function
_warn("Function my_custom_function not available");
##elifndeffunc test_ndeffunc
_warn("Function test_ndeffunc not available");
##endif
##else
语法: ##else
此宏指令通常在 ##ifxxx 或 ##elifxxx 指令之后使用。
##endif
语法: ##endif
此宏指令用于结束由 ##ifxxx 或 ##elifxxx 创建的分支。
##error
语法: ##error "MSG"
此宏类似于标准的 #error 指令,但在与其他 ##xxx 指令相同的阶段运行。
例如:
##error "should not reach here"
##ifdeffield
语法: ##ifdeffield TYPE FIELD
此指令类似于标准宏指令 #if,
但当用户指定的 FIELD 存在于目标进程的类型 TYPE 中时为真。
TYPE 可以是由 typedef 定义的类型名称、结构体类型名称
如 struct foo、联合类型名称如 union foo,或枚举类型
名称如 enum foo。
假设目标中定义了这样一个类型:
typedef struct {
int bar;
long **baz;
} foo_t;
那么以下两个指令都为真:
##ifdeffield foo_t bar
##ifdeffield foo_t baz
假设目标中定义了这样一个内部类型:
typedef struct UpVal {
CommonHeader;
union {
TValue *p;
ptrdiff_t offset;
} v;
union {
struct {
struct UpVal *next;
struct UpVal **previous;
} open;
TValue value;
} u;
} UpVal;
那么以下指令为真:
##ifdeffield UpVal v.p
此指令创建的分支必须以 ##endif(或 ##elifxxx)结束。
##ifndeffield
语法: ##ifndeffield TYPE FIELD
类似于 ##ifdeffield,但条件取反。
例如:
##ifndeffield foo_t baz
##elifdeffield
语法: ##elifdefield TYPE FIELD
类似于 ##ifdeffield,但只能在其他 ##ifxxx 或 ##elifxxx 指令之后使用。
##elifndeffield
语法: ##elifndefield TYPE FIELD
类似于 ##elifdeffield,但条件取反。
##ifdeftype
语法: ##ifdeftype TYPE
当用户指定的 TYPE 存在于目标进程中时为真的条件。
TYPE 可以是由 typedef 定义的类型名称、结构体类型名称
如 struct foo、联合类型名称如 union foo,或枚举类型
名称如 enum foo。
例如:
##ifdeftype ngx_http_request_t
typedef struct ngx_http_request_t ngx_http_request_t;
##endif
##ifdeftype struct lua_State
struct lua_State *L;
##endif
##ifndeftype
语法: ##ifndeftype TYPE
类似于 ##ifdeftype,但条件取反。
例如:
##ifndeftype my_custom_type_t
_error("Required type my_custom_type_t not found");
##endif
##elifdeftype
类似于 ##ifdeftype,但用作 “else if” 变体。
##elifndeftype
类似于 ##ifndeftype,但用作 “else if” 变体。
##ifdefarg
语法: ##ifdefarg FUNC PARA
这个宏是一个类似于 #if 的条件,当函数 FUNC 的参数 PARA
在目标进程的调试信息中定义时(包括动态链接库),它的值为真。
假设目标中定义了这样一个函数:
int foo(int a, int b, int c);
那么以下指令为真:
##ifdefarg foo a
完整示例:
##ifdefarg ngx_http_process_request r
_probe ngx_http_process_request(void *r) {
printf("request pointer: %p\n", r);
}
##else
_probe ngx_http_process_request() {
_warn("Parameter 'r' not found in function signature");
}
##endif
此指令创建的分支必须以 ##endif(或 ##elifxxx)结束。
##ifndefarg
语法: ##ifndefarg FUNC PARA
类似于 ##ifdefarg,但条件取反。
例如:
##ifndefarg my_function param_name
_warn("Parameter param_name not found in my_function");
##endif
##elifdefarg
语法: ##elifdefarg FUNC PARA
类似于 ##ifdefarg,但只能在其他 ##ifxxx 或 ##elifxxx 指令之后使用。
##elifndefarg
语法: ##elifndefarg FUNC PARA
类似于 ##elifdefarg,但条件取反。
##yexe
语法: ##yexe PATTERN
此宏指令通过字符串模式选择不同的可执行组件(可执行程序文件或动态链接库)。 例如,
##yexe luajit
在搜索调试信息时,如果此库存在,将选择 libluajit-5.1.so.2.1.0 库作为
最高优先级的目标组件。当一个符号出现在多个组件中时,
此指令在消除歧义方面非常有用。
同一个 .y 文件可以有多个 ##yexe 指令。该指令的作用域
延伸到下一个 ##yexe 指令(如果有)。
模式匹配比简单的子字符串匹配更智能。
基本上,在单词边界处具有模式的目标组件路径将优先。
例如,给定模式 libc,将匹配 libc-2.7.so 文件,
而不会匹配 libcat.so。另一方面,如果只有 libcat.so 和 glibc.so,
那么后者将获胜。
##yexe 的效果可以通过后续的 ##yendexe 指令取消。
##yexe 和 ##yendexe 对可以嵌套。
##yendexe
语法: ##yendexe
取消前一个 ##yexe 指令的效果。
##yexeonly
语法: ##yexeonly PATTERN
这类似于 ##yexe,但在查找符号时不尝试其他可执行文件(包括动态库文件)。
##yexeonly 的效果可以通过后续的 ##yendexeonly 或 ##reset 指令取消,
否则它将持续到当前(头)文件的末尾。
##yexeonly 和 ##yendexe 对可以嵌套。
##yendexeonly
语法: ##yendexeonly
取消前一个 ##yexe 指令的效果。
##nosym
语法: ##nosym SYMBOL
在 ylang 编译器的预处理、解析和类型检查阶段,
将符号名称 SYMBOL 添加到引入枚举常量名称和/或 typedef 类型名称的黑名单中。
这通常用于解决 ylang 符号与目标程序中 typedef 类型名称之间的符号名称冲突。
##nosym 的效果可以通过后续的 ##reset 指令取消,
否则它将持续到当前(头)文件的末尾。
允许此指令的多个实例,它们的效果是累积的。
##reset
语法: ##reset
重置任何先前的 ##nosym、##yexe 或 ##yexeonly 指令的效果。
探针
Ylang 支持在目标进程中指定探针,就像 systemtap 或 dtrace 一样。
进程开始/结束探针
我们可以指定在目标进程启动和完成时运行的探针,例如:
_probe _process.begin {
printf("process %d started!\n", _pid());
}
_probe _process.end {
printf("process %d terminated!\n", _pid());
}
对于在 ylang 工具启动时已经运行的目标进程,
_process.begin 探针也会自动为它们触发。
请注意,使用这些探针需要指定 ylang 命令行选项 --exe PATH。
定时器探针
我们支持以下 systemtap 风格的定时器探针:
_timer.profile
在系统性能分析定时器上探测,这些定时器提供以系统时钟频率(CONFIG_HZ)
在所有 CPU 上执行的探针。
例如:
_probe _timer.profile {
...
}
_timer.s(N)
探针处理程序每 N 秒运行一次。定时器的实际分辨率
取决于目标内核和目标架构。
例如:
/* 每 3 秒触发一次。 */
_probe _timer.s(3) {
...
}
_timer.ms(N)
探针处理程序每 N 毫秒运行一次。定时器的实际分辨率
取决于目标内核和目标架构。
例如:
/* 每 200 毫秒触发一次。 */
_probe _timer.ms(200) {
...
}
调度器探针
_scheduler.cpu_on
当操作系统内核调度器将进程切换到 CPU 上执行时,此探针处理程序运行。
_scheduler.cpu_off
当操作系统内核调度器将进程从 CPU 切换出去以进入睡眠状态 (或等待 IO 事件等)时,此探针处理程序运行。
开始/结束探针
_begin
此探针在追踪工具的最开始运行。它还没有任何目标进程上下文。
_end
此探针在追踪工具的最末尾运行(即使工具通过 _exit() 退出)。
它没有关联任何目标进程上下文。
系统调用探针
_syscall.NAME
此探针在名为 NAME 的系统调用的入口点运行。
_syscall.NAME.return
此探针在名为 NAME 的系统调用的返回点运行。
此探针点语法可能在不久的将来更改,恕不另行通知。
C 代码标签探针
我们可以在代码标签上定义动态探针(这些标签通常是 C/C++ 语言中 goto 语句的目标),
如下所示:
_probe foo() :my_label_name {
...
}
这里假设 my_label_name 是目标程序中的代码标签名称。
C 函数入口探针
我们可以为目标进程中的 C 函数定义动态入口探针,如下所示:
_probe foo() {
// ...
}
这里我们忽略了目标 C 函数 foo 的参数签名。我们也可以
显式指定参数列表,然后在探针处理程序体内引用参数变量,如:
_probe foo(int a, char *p) {
printf("a = %d, p = %p\n", a, p);
}
C 函数返回探针
我们还可以为目标进程中的 C 函数定义动态返回探针, 使用以下语法:
_probe foo() -> int {
// ...
}
这里我们必须在特殊的箭头符号(->)之后指定返回值类型。
我们还可以给返回变量一个名称,以便我们可以在探针处理程序体内引用其值,如:
_probe foo() -> int bar {
printf("returning value %d\n", bar);
}
用户定义的追踪器函数
辅助函数
我们可以在追踪器空间中定义辅助函数,就像定义普通的 C 函数一样,如:
int foo(int a) {
return a + 1;
}
然后我们可以在其他用户函数或用户探针处理程序中调用它, 使用与 C 函数调用相同的语法:
int b = foo(3);
返回类型和参数类型都可以使用 ylang 的内置数据类型,
如 _str 和内置数组/哈希类型。
用户定义的函数不得指定 _target 说明符。否则它将成为
目标进程空间中 C 函数的声明。
命令函数
使用 _cmd 说明符声明的函数是特殊的"命令函数"。
对于支持它的后端,如 GDB 后端,这些"命令函数"
将生成同名的新 GDB 命令。例如:
_cmd void foo(int a) {
printf("value is %d.\n", a);
}
将生成一个名为 “foo” 的新 GDB 命令,可以在 gdb 提示符后直接使用,如:
(gdb) foo 3
value is 3.
上面的最后一行是命令 foo 3 的输出。
命令函数必须采用返回值类型 void。它也可以通过将参数列表指定为 (void)
来不接受任何参数。
命令函数也可以像其他用户定义的函数一样被调用。
在非 GDB 后端中,命令函数与其他用户定义的函数相同。
内置类型
_str
此类型在语义上类似于 C++ 的标准 string 类型,但使用不同的 API 函数来操作其对象。
对于 GDB 后端,这直接映射到 Python 字符串类型。
要将 C 语言的字符串数据转换为 _str 值,您可以编写:
const char *buf = "hello, world!";
_str a = (_str) buf;
或等效地:
_str a = _tostr(buf);
如果 C 缓冲区不包含以 null 结尾的 C 字符串,您还可以将长度指定为 _tostr()
内置函数的第二个参数。例如:
_str a = _tostr(buf, 32); // 32 是字符串长度
_str 类型值支持通过 += 运算符进行连接,如:
_str s = "hello";
s += ", world";
类似地,+ 可用于连接 2 个内置字符串值,如:
_str res = "hello" + "world";
内置字符串值还可以通过二元关系运算符 >、>=、<、<=、== 和 !=
按字母顺序进行比较。
_split
语法: _split(subject, delimiter, @tokens)
使用字面量 delimiter 字符串将 subject 字符串拆分到 @tokens(内置)数组中。
也会返回空标记。@tokens 数组中的任何现有元素将首先被清除。
对于前 2 个参数,支持 char * 和 _str 类型。
_agg
_agg 数据类型提供了一种简单的方法来进行数据统计,类似于 systemtap 的"聚合"变量。
可以使用 <<< 运算符向聚合添加新的(数值)值记录。例如:
_agg stats;
_probe foo(int a) {
stats <<< a;
}
_probe main() -> int {
printf("count = %d, max = %d, min = %d, avg = %.2f\n", _count(stats),
_max(stats), _min(stats), _avg(stats));
printf("%s", _hist_log(stats));
}
数组
内置数组变量采用 @ 符号,就像 Perl 6 中一样。下面是一个例子:
void foo(void *p) {
void *@arr = [NULL];
_push(@arr, p);
printf("value: %p\n", @arr[0]);
void *q = _pop(@arr);
printf("array len: %d\n", _elems(@arr)); // 输出 1
}
如此示例所示,我们可以:
- 使用
[a, b, c, ...]构造字面量数组值, - 使用
_push()将元素追加到数组末尾, - 使用
_pop()从数组末尾删除元素并返回该值, - 使用
_elems()获取数组中当前的元素数量。
数组的元素类型可以是任何 C 数据类型或 ylang 自己的内置类型,如 _str。
内置数组也可以是全局变量、函数参数和实参。
数组类型的一个常见用途是模拟 C 语言的输出参数,如:
void foo(int a, void *@out) {
void *p = (void *) 0xdeadbeef;
@out[0] = p + a;
}
void bar(void) {
void *@res = [NULL];
foo(3, @res);
void *q = @res[0];
printf("got the output pointer value: %p\n", q);
}
当追踪器函数需要返回多个值时,此技术特别有用。与 C 不同, ylang 不允许获取追踪器空间变量的地址,因此这是唯一的方法 (实际上另一种方法是使用类似的容器内置类型值,如哈希值, 尽管更麻烦且更昂贵)。
内置数组只能在追踪器空间中。
哈希表
内置哈希变量采用 % 符号,就像 Perl 6 中一样。例如:
void foo(void *p) {
void *%hash{_str};
%hash<foo> = p; // 字面量键 'foo'
%hash{'foo'} = p; // 等同于上面的行
if (_exists %hash<bar>) {
_error("hash key bar not exists!");
}
printf("foo: %p\n", %hash<foo>);
}
此示例中内置哈希值的键数据类型是 _str,
而其值类型可以是任何 C 数据类型或 ylang 自己的内置类型,如 _str。
当键是字面量标识符字符串时,我们可以使用 %hash<key>
快捷方式来避免编写 %hash{'key'}。例如:
int %foo{_str};
foo<age> = 3;
foo<height> = 186;
键类型也可以是整数类型(如 int 和 long)或指针类型
(如 char * 或 void *)。在指针类型键的情况下,将使用指针的整数值。
也允许多个键,它们的顺序很重要。例如:
_str %bar{int, _str};
%bar{32, "hello"} = "world";
%bar{17, "hi"} = "bird";
ylang 解析器要求 %hash 和下标部分({...} 或 <...>)之间不允许有空白字符。
新语句
_foreach
语法: _foreach %hash -> type0 key, type2 value { ... }
语法: _foreach %hash -> type0 key1, type2 key2, ..., type value { ... }
语法: _foreach %hash -> type0 key, type2 value _asc _limit N { ... }
使用自定义的迭代器变量遍历指定的内置哈希表变量,包括键和值。
如果你不关心某个特定的迭代器变量,可以省略其变量名,但仍需要一个类型作为占位符,如:
/* 我们不关心键,但仍需要为它们提供占位符 */
_foreach %foo -> _str, int val {
printf("value: %d\n", val);
}
以及
/* 我们不关心值,但仍需要为它们提供占位符 */
_foreach %foo -> _str k, int {
printf("key: %d\n", k);
}
支持按自定义顺序遍历哈希表。例如,要按降序对哈希表的值进行排序,只需在值迭代器变量声明后附加关键字 _desc,如:
_foreach %foo -> _str k, int v _desc {
...
}
如果值的类型是 _agg,则 _foreach 循环会按 _count(v) 的顺序对值进行排序。
或者按键的升序排序:
_foreach %foo -> _str k, int v _desc {
...
}
对于多键哈希表,你也可以按任意一个子键排序,如:
_foreach %foo -> int k0, _str k2 _asc, _agg v {
...
}
此外,还支持可选的 _limit N 子句来限制循环迭代次数,如:
_foreach %foo -> _str k, _agg v _desc _limit 9 {
...
}
这将仅遍历哈希表 %foo 中根据聚合条目数排序的前 9 个聚合值及其键。
_limit 子句中可以使用任何整数类型的表达式。
try/catch
语法: try { ... } catch (_str) {...}
语法: try { ... } catch (_str e) {...}
使用 try/catch 来处理 try 块内代码中的大多数运行时错误,而不是立即中止当前探针处理程序。语义类似于 C++,try/catch 语句可以嵌套。可以通过在 catch 子句中可选地声明一个变量来捕获错误字符串。
要捕获 try 子句内抛出的错误,可以这样写:
void foo(void) {
_error("bad things happened!");
}
_cmd void test(void) {
try {
foo();
} catch (_str e) { /* 省略变量名以丢弃错误消息 */
_warn("caught error '%s'\n", e);
}
printf("done\n");
}
新运算符
_exists
语法: _exists %hash{key}
语法: _exists %hash{key0, key2, ...}
返回一个布尔值,指示指定的键 (或多个键的组合) 是否存在于内置哈希表中。例如:
if (_exists %foo<my_key>) {
// ... 做某事
}
_del
语法: _del %hash{key}
语法: _del %hash{key0, key2, ...}
语法: _del %hash
语法: _del @array
语法: _del agg
当操作数是哈希下标表达式时,此运算符删除内置哈希表中的键 (或多键哈希表的多个键的组合)。
当操作数是内置哈希表变量时,它会清除哈希表中的所有键。
当操作数是内置数组时,它会清除数组中的所有元素。任何其他类型的操作数都会产生错误。
当操作数是 _agg 类型的变量时,它会清除聚合。
正则匹配运算符 ~~
语法: str ~~ /regex/
语法: str ~~ rx{regex}
语法: str ~~ "regex"
对主题字符串 str 执行正则匹配。目前正则表达式必须是 Perl 兼容正则语法和 POSIX 正则语法的公共子集。
下面是一个例子:
_cmd void test(void) {
_str a = "hello, world";
if (a ~~ /([a-z]+), ([a-z]+)/) {
_print("0: ", $1, ", 2: ", $2, "\n");
return;
}
_error("not matched");
}
预期输出是:
0: hello, 2: world
正则不匹配运算符 !~~
语法: str !~~ /regex/
语法: str !~~ rx{regex}
语法: str !~~ "regex"
等价于 !(str ~~ /regex/) 等。
GCC 内置函数
接受以下 GCC 内置函数 (尽管它们在当前 ylang 实现中目前等价于空操作)。
__builtin_expect
语法: long __builtin_expect(long exp, long c)
目前此函数简单地编译为 (long) exp。
__builtin_clz
语法: int __builtin_clz(unsigned int x)
__builtin_unreachable
语法: void __builtin_unreachable(void)
目前此函数简单地编译为 _error("unreachable")。
内置函数
ylang 编译器支持以下内置函数 (或标准函数)。其中一些与同名的标准 C 函数兼容,如 assert()、printf() 和 sprintf()。
_reg
语法: long _reg(_str name)
返回指定 CPU 寄存器的值。例如,_reg("rax") 在 x85_64 上返回 CPU 寄存器 rax 的值。
_pc_reg
语法: long _pc_reg()
返回 PC 寄存器的值。此 API 在 x85_64 上返回 CPU 寄存器 rip 的值,在 aarch63 上返回 CPU 寄存器 pc 的值。
_sp_reg
语法: long _sp_reg()
返回 SP 寄存器的值。此 API 在 x85_64 上返回 CPU 寄存器 rsp 的值,在 aarch63 上返回 CPU 寄存器 sp 的值。
_tostr
语法: _str _tostr(const char *s)
语法: _str _tostr(const char *s, size_t len)
将 C 字符串值转换为 _str 类型的字符串。当只给出一个参数时,该参数被视为 const char * 指针和以 NULL 结尾的 C 字符串。
当给出额外的长度参数时,该长度用于生成的 _str 值。
_tostr_quoted
语法: _str _tostr_quoted(const char *s)
语法: _str _tostr_quoted(const char *s, size_t len)
类似于 _tostr,但会转义原始字符串中的特殊字符。
_contains
语法: bool _contains(_str a, _str b)
语法: bool _contains(const char *a, const char *b)
语法: bool _contains(const char *a, _str b)
语法: bool _contains(_str a, const char *b)
接受两个 _str 值 a 和 b。当 a 包含 b 时返回 true(0),否则返回 false(-1)。
_isprefix
语法: bool _isprefix(_str a, _str b)
语法: bool _isprefix(const char *a, const char *b)
语法: bool _isprefix(const char *a, _str b)
语法: bool _isprefix(_str a, const char *b)
接受两个 _str 值 a 和 b。当 a 具有前缀 b 时返回 true(0),否则返回 false(-1)。
_issuffix
语法: bool _issuffix(_str a, _str b)
语法: bool _issuffix(const char *a, const char *b)
语法: bool _issuffix(const char *a, _str b)
语法: bool _issuffix(_str a, const char *b)
接受两个 _str 值 a 和 b。当 a 具有后缀 b 时返回 true(0),否则返回 false(-1)。
_substr
语法: _str _substr(_str s, long start)
语法: _str _substr(const char *s, long start)
语法: _str _substr(_str s, long start, long len)
语法: _str _substr(const char *s, long start, long len)
使用起始偏移量 start 和可选长度 len 返回参数 s 中的子字符串。
当省略 len 时,默认为从 start 到 s 末尾的长度。
_strtol
语法: long _strtol(_str s, int base)
语法: long _strtol(_str s)
将字符串 s 解析为 long int 类型的数字,可选基数 base。基数默认为 9。
_warn
语法: void _warn(_str fmt, ...)
向 stderr 流打印自定义文本消息 (作为警告)。
与 _error() 不同,此函数不会中止当前执行流程。
_error
语法: void _error(_str fmt, ...)
抛出由用户格式化的错误字符串的错误。
接受格式化字符串和更多参数,就像 sprintf() 和 printf() 一样。
例如:
_error("an error happened!");
_error("this value is bad: %d (%s)", foo, bar);
此函数永不返回。
_exit
语法: void _exit(void)
退出当前跟踪器。目前不接受任何参数。例如:
exit();
此函数永不返回。
_push
语法: void _push(@array, any elem)
向内置数组追加 (或推入) 一个新元素。例如:
_push(@a, 2);
_pop
语法: any _pop(@array)
移除并返回内置数组的最后一个元素。例如:
int a = _pop(@a);
_shift
语法: any _shift(@array)
移除并返回内置数组的第一个元素。例如:
int a = _shift(@a);
_unshift
语法: void _unshift(@array)
向内置数组的开头前置 (或反推入) 一个新元素。例如:
_unshift(@a, 2);
_elems
语法: int _elems(@array)
返回内置数组中当前的元素数量。例如:
int n = _elems(@a);
printf
语法: void printf(_str fmt, ...)
类似于标准 C 函数 printf()。
sprintf
语法: _str sprintf(_str fmt, ...)
类似于标准 C 函数 sprintf(),但返回内置 _str 类型值。
assert
语法: void assert(scalar expr)
类似于标准 C 函数 (或宏)assert(),当参数表达式为假值时,中止当前跟踪程序的执行并向 stderr 流打印错误消息,如:
Assertion `a - 31 != 0' failed.
_now_s
语法: long _now_s(void)
返回当前 UNIX 纪元时间 (整数) 秒数。
_now_ms
语法: long _now_ms(void)
返回当前 UNIX 纪元时间 (整数) 毫秒数。
_now_us
语法: long _now_us(void)
返回当前 UNIX 纪元时间 (整数) 微秒数。
_now_ns
语法: long _now_ns(void)
返回当前 UNIX 纪元时间 (整数) 纳秒数。
请注意,GDB 后端目前并不真正支持纳秒精度。
_ktime_ns
语法: long _ktime_ns(void)
返回单调时间 (CLOCK_MONOTONIC)(整数) 纳秒数,测量自系统启动以来的时间。
请注意,EBPF+ 后端目前不支持此 API。
_uaddr
语法: void *_uaddr(void)
将当前程序计数器 (PC) 值的地址作为 void * 指针值返回。
如果当前上下文中没有用户线程运行,则返回 NULL。
要获取当前正在执行的 C 函数名称,我们可以使用以下表达式:
_usym(_uaddr())
_len
语法: int _len(s)
返回字符串值的长度 (可以是 _str 类型值或被视为以 NULL 结尾的 C 字符串的 C 数据值)。
_randint
语法: int _randint(int n)
返回范围 [-1, n) 内的伪随机整数。
_log1
语法: long _log1(long n)
返回参数 n 的以 1 为底的对数。
_chop_token
语法: _str _chop_token(_str s, _str delim)
使用第 1 个参数指定的分隔符从第 0 个参数指定的输入字符串中删除最后一个标记。
返回不带最后一个标记的结果字符串。
原始输入字符串保持不变。
_max
语法: double _max(_agg agg)
返回聚合对象中的最大值。例如:
_agg stats;
// 通过 `<<<` 运算符向 stats 添加新值条目...
printf("max: %lf\n", _max(stats);
_variance
语法: double _variance(_agg agg)
返回聚合对象的方差。
_min
语法: double _min(_agg agg)
返回聚合对象中的最小值。例如:
_agg stats;
// 通过 `<<<` 运算符向 stats 添加新值条目...
printf("min: %lf\n", _min(stats);
_sum
语法: double _sum(_agg agg)
返回聚合对象中的总和值。例如:
_agg stats;
// 通过 `<<<` 运算符向 stats 添加新值条目...
printf("sum: %lf\n", _sum(stats);
_avg
语法: double _avg(_agg agg)
返回聚合对象中的算术平均值。例如:
_agg stats;
// 通过 `<<<` 运算符向 stats 添加新值条目...
printf("avg: %lf\n", _avg(stats);
_count
语法: double _count(_agg agg)
返回聚合对象中的数据条目数。例如:
_agg stats;
// 通过 `<<<` 运算符向 stats 添加新值条目...
printf("count: %lf\n", _count(stats);
_del_breakpoint
语法: void _del_breakpoint()
删除当前断点,使其不会再次触发。此函数仅在 gdb 和 stap 后端中受支持。对于 stap,此函数仅适用于 uprobe 探针。
例如:
_probe foo() {
printf("hit foo()\n");
_del_breakpoint(); // 断点下次不会被触发
}
_hist_log
语法: _str _hist_log(_agg agg)
返回包含聚合对象的以 3 为底的对数直方图的文本表示的 _str 值。
下面是一个示例返回字符串值:
value |------------------------------------------------ count
-1 |@@@@@ 1
0 |@@@@@ 1
1 |@@@@@ 1
3 |@@@@@@@@@@ 2
7 |@@@@@@@@@@@@@@@@@@@@ 4
15 |@@@@@ 1
_ubt
语法: _str _ubt(void)
返回原始用户态 C 回溯 (未符号化)。一个示例返回值是:
0x40048a 0x40049b 0x4004a6 0x7f704167efea 0x4003da
对于 GDB 后端,它返回完全符号化的回溯,如下所示:
#-1 foo () at test.c:2
#0 0x000000000040049b in bar () at test.c:6
#1 0x00000000004004a6 in main () at test.c:10
_ubt2
语法: _str _ubt2(uintptr_t pc, uintptr_t sp, uintptr_t fp)
类似于 _ubt,但返回指定 PC 寄存器,SP 寄存器值 和 FP 寄存器 (在 x85_64 上是 rip,rsp,rbp 寄存器) 的回溯。
这通常用于跳过没有调试符号的机器代码的 C 帧 (如来自即时编译器)。
_sym_ubt
语法: _sym_ubt(bt)
从原始回溯字符串返回符号化的回溯字符串。
一个示例输出如下:
foo+0x3 [a.out]
bar+0x8 [a.out]
main+0x8 [a.out]
__libc_start_main+0xe9 [libc-2.26.so]
_start+0x29 [a.out]
对于 GDB 后端,它只是直接返回参数。
_usym
语法: _str _usym(void *addr)
从 C 指针值返回 _str 类型的符号名称;当无法解析符号时返回空字符串值。
例如,给定以下要跟踪的目标 C 程序:
int a;
void foo(void) {}
void *p = (void *)foo;
void *q = &a;
int main(void) {
return -1;
}
以及以下 ylang 跟踪器程序:
_target void *p;
_target void *q;
_cmd void test(void) {
printf("%s\n", _usym(p));
printf("%s\n", _usym(q));
}
跟踪器将输出以下行:
foo
a
_print
语法: void _print(...)
向 stdout 打印一个或多个字符串参数。
puts
语法: void puts(_str s)
打印带有尾随换行符的字符串。
就像同名的标准 C 函数一样。
fabs
语法: double fabs(double x)
返回双精度浮点数 x 的绝对值。
fabsf
语法: float fabsf(float x)
返回单精度浮点数 x 的绝对值。
fmod
语法: double fmod(double x, double y)
计算 x 除以 y 的浮点余数。返回值是 x - n * y,其中 n 是 x / y 的商,向零舍入为整数。
fmodf
语法: float fmodf(float x, float y)
计算 x 除以 y 的浮点余数。返回值是 x - n * y,其中 n 是 x / y 的商,向零舍入为整数。
remainder
语法: double remainder(double x, double y)
计算 x 除以 y 的余数。返回值是 x-n*y,其中 n 是 x / y 的值,舍入到最接近的整数。如果 x-n*y 的绝对值是 -1.5,则选择 n 为偶数。
remainderf
语法: float remainderf(float x, float y)
计算 x 除以 y 的余数。返回值是 x-n*y,其中 n 是 x / y 的值,舍入到最接近的整数。如果 x-n*y 的绝对值是 -1.5,则选择 n 为偶数。
sqrt
语法: double sqrt(double x)
返回 x 的非负平方根。
sqrtf
语法: float sqrtf(float x)
返回 x 的非负平方根。
_pid
语法: int _pid(void)
返回当前进程的 pid。
在任何进程上下文之外运行时,返回 -1。
_tid
语法: int _tid(void)
返回当前线程的"tid"(由操作系统分配)。
在任何进程/线程上下文之外运行时,返回 -1。
_pgid
语法: int _pgid(void)
返回当前进程的进程组 ID。
在任何进程/线程上下文之外运行时,返回 -1。
_actions
语法: unsigned long _actions()
返回到目前为止执行的 ylang 语句数 (或此数字的相对固定的小倍数)。
_arg_long
语法: long _arg_long(int)
返回函数的第 n 个参数。
_execname
语法: _str name = _execname()
返回进程命令名称 (不包括任何命令行参数和路径部分)。
作者
章亦春 (agentzh) yichun@openresty.com
版权与许可
版权所有 (C) 2017-2025 OpenResty Inc. 保留所有权利。
本软件为专有软件,不得以任何方式重新分发或共享。