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
  • 一元運算子 -
  • 二元運算子 +-*/%..
  • 二元運算子 >>=<<===!=
  • 二元運算子 andor
  • 字尾 [KEY]
  • 字尾 .KEY
  • 括號(()
  • 可變引數(...
  • 布林值(truefalse
  • 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 內建函式:

  • require
  • print
  • type
  • select
  • error
  • tostring
  • tonumber
  • ipairs(僅作為 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:用於在追蹤器空間中儲存布林值 truefalse
  • 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) 迴圈也遍歷表的陣列部分。
  • 運算子 % 應該直接作用於浮點數。
  • andor 運算子表示式不應該只返回布林值。
  • 支援從類數字字串值轉換為數字。
  • 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 的 ffi API,用於操作 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 風格的內建雜湊表變數和陣列變數。
  • 新增內建探針,如 beginendsyscall.* 等。

返回目錄

作者

Yichun Zhang (agentzh) yichun@openresty.com

返回目錄

版權與許可

版權所有 (C) 2018-2025 OpenResty Inc. 保留所有權利。

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

返回目錄

另請參閱

返回目錄