Name

ylua - Compiler for the ylua language for writing Lua apps' tracing tools in a Lua-like language.

Table of Contents

Language features

Language syntax examples

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

Back to TOC

Lua syntax supported

The following Lua operators and syntax are supported:

  • unary #
  • unary not
  • unary -
  • binary +, -, *, /, %, ..
  • binary >, >=, <, <=, ==, !=
  • binary and, or.
  • postfix [KEY]
  • postfix .KEY
  • parentheses (())
  • vararg (...)
  • booleans (true, false)
  • if/elseif/else statements
  • return statement
  • do statement
  • for v = fr, to, step do loop statement
  • for i, e in ipairs(tb) loop statement
  • for k, v in pairs(tb) loop statement
  • while statement
  • repeat ... until statement
  • break statement
  • local variable declarations (with multiple variables and initializer expressions)
  • assignment statement (with multiple assignment support)
  • nil value
  • function call syntax foo(...), foo"...", foo{...}, and etc.

Back to TOC

Built-in functions supported

Lua built-in functions

The following Lua built-in functions are supported:

  • require
  • print
  • type
  • select
  • error
  • tostring
  • tonumber
  • ipairs (only as the exp-list in the for ... in loop)
  • pairs (only as the exp-list in the for ... in loop)
  • table.maxn

Back to TOC

Extra built-in functions

The following extra standard functions are supported:

  • dump

    Dump details of the Lua value argument.

  • out

    Similar to print(), but do not output a trailing newline character automatically

  • identity

    Directly return the argument value.

  • exit

    Exit the probing session (i.e., the whole tracing tool).

  • warn

    Emit a warning message to stderr.

  • contains

    Returns true if the 1st string argument contains the 2nd string argument, or false otherwise.

  • upval

    syntax: upval(name)

    syntax: upval(func, name)

    Returns the upvalue of the name specified by the name argument. The associated function can be either the current function being probed or the one explicitly specified by the func argument. Only Lua functions take named upvalues. This upval() function can also be used in Lua function probe specifier, as in

    probe (upval(require "foo".blah, "uvname")) (a, b)
        ...
    end
    

Back to TOC

Probes

Lua function probes

Lua function entry probes

Dynamic probes on Lua function entry points are supported. Certain Lua built-in functions implemented directly in hand-crafted assembly code might miss the probes, however, as some of the API functions under the math namespace.

Below is an example:

probe foo(a)
    print("arg a = ", a)
end

It prints out the value of the actual argument a of the global Lua function foo in the target every time that function is entered (due to a function call).

Vararg syntax is also supported when the number of actual Lua function arguments in the target process may change. For example:

probe foo(...)
    for i = 1, select('#', ...) do
        print(i, ": ", select(i, ...))
    end
end

This probe handler will output all the actual arguments in every invocation of the global Lua function foo in the target. One typical output might look like this:

1: 3
2: hello
3: 3.140000

In case the target Lua function might return composite Lua values like Lua tables, we should use the dump() builtin function instead in this example, as in

probe foo(...)
    for i = 1, select('#', ...) do
        print(i, ": ", dump(select(i, ...)))
    end
end

One sample output is like below:

1: 171
2: "hello"
3: true
4: table (GCtab*)0x7f96f754d830 (narr=0, nrec=1):
	key:
		"dogs"
	value:
		-3.140000

Note how the output of dump() differ from printing out the Lua target values directly.

For Lua function entry probes, the parameter variables, upvalue variables, and global variables can all be referenced. Local variables which are not parameters cannot be referenced in such probe handlers since they are not initialized at that point, naturally. Below is an example:

probe foo(a)
    print(a + b)
end

Compiling this ylua code example will yield the following warning:

WARNING: bar: symbol 'b' is assumed to be an upvalue or a global varible in the target Lua process at test.ylua line 2

Obviously, the referenced variable b is not declared and will be assumed to be either an upvalue for the function foo in the target, or a global Lua variable of the current Lua thread when the probe is fired. An upvalue of the current Lua function take precedence over a global variable lookup, for obvious reasons.

Any Lua primary expressions can be used as probe specifier, as in

probe (package.loaded["io"].open)(file_name, mode)
    print("opening file ", file_name, " with mode ", mode)
end

Or even with tracer-space built-in function calls, as in

probe (require "io".open)(file_name, mode)
    print("opening file ", file_name, " with mode ", mode)
end

Note that the parentheses are required to enclose the expression otherwise there is grammatical ambiguities.

Back to TOC

Lua function return probes

Dynamic probes on Lua function return points are supported. Tailcalls might miss our probes, however, due to their inherient "goto" nature.

Consider the following example:

probe foo -> ()
    print("function foo returning!")
end

This probe handler will prints out the line function foo returning! every time the global Lua function foo() is returned in the target. Here we do not care whether such function exits return any values. But when we do, we can inspect the return values like this:

probe foo -> (a, b)
    print("returning ", a, " and ", b)
end

This probe will output the first 2 return values of every return of the target Lua function.

Similar to the Lua function entry probes, the Lua vararg syntax (...) is also supported in return probes to inspect all the return values when the number of them are not fixed or not known ahead. Below is such an example:

probe foo -> (...)
    for i = 1, select('#', ...) do
        print(i, ": ", select(i, ...))
    end
end

One typical output is like this:

1: 171
2: hello
3: true
4: -3.140000

We should use the dump() built-in function in case the return values might be composite Lua values like Lua tables, as in

probe foo -> (...)
    for i = 1, select('#', ...) do
        print(i, ": ", dump(select(i, ...)))
    end
end

For Lua function return probes, all local variables visible to the corresponding Lua function return point, upvalues, and global variables can all be accessed. Note that the Lua VM might optimize certain local variables away if the return point does not really reference those local variables.

Any Lua primary expressions can be used as probe specifier, as in

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

Or even with tracer-space built-in function calls, as in

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

Note that the parentheses are required to enclose the expression otherwise there is grammatical ambiguities.

Back to TOC

Type system

The ylua language has the following value types:

  • tv: for storing Lua values in the target process (or "TValue" pointers).
  • num: for storing double-precision numbers in the tracer space.
  • int: for storing 32-bit signed integers in the tracer space.
  • bool: for storing boolean values true and false in the tracer space.
  • str: for storing strings in the tracer space.
  • nil: for the nil value (or void) in the tracer space.
  • func: for the built-in or user-defined ylua functions in the trace space.

A ylua variable in the tracer space can take all the data types above except the nil type. When a ylua variable is initialized with nil, it is of the tv type. A tv typed variable can also be assigned to by a nil-typed expression.

When a function returns no value (like the built-in function print()), it takes the return value type of nil.

Every ylua variable can only take data type and its type must be determined at compile time and must not change at run time.

All the ylua variables for storing values in the target process space must be of the type tv. And naturally, all values coming from the target process take the type tv.

Back to TOC

Author

Yichun Zhang (agentzh) yichun@openresty.com

Back to TOC

Copyright & Licenses

Copyright (C) 2018-2019 OpenResty Inc. All rights reserved.

This software is proprietary and must not be redistributed or shared at all.

Back to TOC