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. 保留所有權利。

本軟體為專有軟體,不得以任何方式重新分發或共享。

返回目錄