OpenResty XRay™ Y 语言用户参考手册

创建动态追踪分析器时使用的 Y 语言文档,紧密遵循 C 语言语法

OpenResty XRay Y 语言用户参考手册

目录

语言参考

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.hstddef.h 这样的标准 C 头文件, 不应通过 #include 指令将它们包含在 ylang 程序中。但是,您可以 自由地通过 #include 包含其他 ylang 文件。

值得注意的是,由于我们直接在用户 ylang 源代码上运行 gcc 的预处理器, 因此支持完整的 gcc 宏指令(包括可变参数宏!)。

Ylang 还有各种自己的语言扩展:

Back to TOC

支持所有标准 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.soglibc.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
}

如此示例所示,我们可以:

  1. 使用 [a, b, c, ...] 构造字面量数组值,
  2. 使用 _push() 将元素追加到数组末尾,
  3. 使用 _pop() 从数组末尾删除元素并返回该值,
  4. 使用 _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;

键类型也可以是整数类型(如 intlong)或指针类型 (如 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)

接受两个 _strab。当 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)

接受两个 _strab。当 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)

接受两个 _strab。当 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 时,默认为从 starts 末尾的长度。

返回目录

_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,其中 nx / y 的商,向零舍入为整数。

返回目录

fmodf

语法: float fmodf(float x, float y)

计算 x 除以 y 的浮点余数。返回值是 x - n * y,其中 nx / 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. 保留所有权利。

本软件为专有软件,不得以任何方式重新分发或共享。

返回目录