Skip to content

4.7 - 调试接口

Lua没有内置的调试工具。取而代之的是提供特殊的接口函数和钩子 hooks。这些接口可用于构建不同的调试器、监控、以及其他需要使用解释器“内部信息”的工具。

lua_Debug

C
typedef struct lua_Debug {
  int event;
  const char *name;           /* (n) */
  const char *namewhat;       /* (n) */
  const char *what;           /* (S) */
  const char *source;         /* (S) */
  size_t srclen;              /* (S) */
  int currentline;            /* (l) */
  int linedefined;            /* (S) */
  int lastlinedefined;        /* (S) */
  unsigned char nups;         /* (u) 上值的数量 */
  unsigned char nparams;      /* (u) 参数数量 */
  char isvararg;              /* (u) */
  char istailcall;            /* (t) */
  unsigned short ftransfer;   /* (r) 被转移的第一个值的索引 */
  unsigned short ntransfer;   /* (r) 被转移的值的数量 */
  char short_src[LUA_IDSIZE]; /* (S) */
  /* 私有部分 */
  other fields
} lua_Debug;

此结构体用于记录各种有关于函数或运行记录的信息部分。lua_getstack在最近的一次调用中只会填充此结构体的私有部分。如果需要填充lua_Debug的其他字段以得到有用的信息,那么你必须使用一个合适的参数来调用lua_getinfo函数。(具体说来,如果要获取某个字段,你必须要将上边代码块中对应字段后注释中用括号包围的字母传入到lua_getinfo的 what 参数中。)

lua_Debug中的各字段的含义如下:

  • source: 所创建的函数的代码块源码。当 source 由字符 '@' 为首时,则意味着函数定义在 '@' 后跟的文件中。当 source 由字符 '=' 为首时,其后边的内容中对源码的描述是依赖于用户的。否则,该函数就是定义在 source 表示的字符串中的。
  • srclen: 字符串 source 的长度。
  • short_src: 一个“可打印”版本的 source ,用于错误信息中。
  • linedefined: 函数定义起始对应的行号。
  • lastlinedefined: 函数定义末尾对应的行号。
  • what: 当这个字符串内容为"Lua"时表示该函数是个Lua函数,为"C"时则是一个C函数,为"main"时则表示代码块的主体部分。
  • currentline: 表示给定函数的执行到哪一行了。当提供不了有效行数信息的时候,currentline 会被置为 -1。
  • name: 给定函数的合理名称。因为函数在Lua中是一等公民值,所以其没有固定的名称:有些函数是多个全局变量共有的值,其他的可能只是表中的字段。lua_getinfo函数会检查函数的调用方式以找到一个合适的名字。如果没有找到,那么 name 会被置为NULL。
  • namewhat: 用于解释字段 namenamewhat 可以是"global"、"local"、"method"、"field"、"upvalue"、或者""(空字符串),取决于该函数的调用方式。(在似乎没有合适的选择时,Lua会使用空字符串。)
  • istailcall: 如果该函数是由尾调用形式唤起的则为true。在这种情况下,这一层的调用者不在栈中。
  • nups: 该函数的上值数量。
  • nparams: 该函数的参数数量(C函数中始终是0)。
  • isvararg: 该函数是否为可变参数函数(对于C函数来说始终为 true )。
  • ftransfer: 被“转移”的第一个值在栈中的索引,即调用中的参数或返回语句中的返回值。(其他的值在第一个值其后边的连续索引中。)通过这个索引,你就可以使用lua_getlocallua_setlocal来访问或更改这些值。该字段只在调用 hook 期间标记第一个参数、或是返回 hook 中标记第一个返回的值中有意义。(对于调用 hook,该值始终为 1。)
  • ntransfer: 被“转移”(参见上一条)的值的数量。(对于Lua函数的调用,这个值总是等于 nparams。)

lua_gethook

[-0, +0, -]

lua_Hook lua_gethook (lua_State *L);

返回当前函数的 hook。

lua_gethookcount

[-0, +0, -]

int lua_gethookcount (lua_State *L);

返回当前函数的 hook 数量。

lua_gethookmask

[-0, +0, -]

int lua_gethookmask (lua_State *L);

返回当前 hook 的掩码。

lua_getinfo

[-(0|1), +(0|1|2), m]

int lua_getinfo (lua_State *L, const char *what, lua_Debug *ar);

获取某个函数或某个函数调用的信息。

如需获取某个函数调用的信息,那么参数 ar 必须为一个有效的活动记录,其可以是由在此之前调用lua_getstack所填充的,或是作为 hook 的参数给出的(参见lua_Hook)。

如需获取某个函数的相关信息,你需要将其压入栈中并将字符串 what 的开头设为 '>' 字符。(这种情况下,lua_getinfo会从栈上弹出这个函数。)例如,想知道函数 f 的定义,你可以这样写:

C
    lua_Debug ar;
    lua_getglobal(L, "f");  /* 获取全局函数 f */
    lua_getinfo(L, ">S", &ar);
    printf("%d\n", ar.linedefined);

字符串 what 中的各个字符都会使得结构体 ar 中的某些特定的字段被设置,或是将某个值压入到栈中。(这些字符在结构体lua_Debug中各字段后的注释中标明了,在其中是由括号包围起来的字符。)各字符选项的含义如下所示:

  • 'f': 将运行在给定层级中的函数压入栈中。
  • 'l': 填充 currentline 字段。
  • 'n': 填充 name、namewhat 字段。
  • 'r': 填充 ftransfer、ntransfer 字段。
  • 'S': 填充 source、short_src、linedefined、lastlinedefined、what 字段。
  • 't': 填充 istailcall 字段。
  • 'u': 填充 nups、nparams、isvararg 字段。
  • 'L': 将一个表压入栈中,该表的索引是在函数上关联的源代码行号,即那些可以打断点的行(不包括空行和注释)。如果该选项与选项 'f' 一起使用,那么再选项 'f' 压完栈之后再将这个表入栈。这是唯一可能抛出内存错误的选项。

该函数返回0则表示 what 中有无效的字符选项,但此时其他的有效字符选项仍然会被处理。

lua_getlocal

[-0, +(0|1), -]

const char *lua_getlocal (lua_State *L, const lua_Debug *ar, int n);

获取给定的活动记录或函数中的局部变量或临时值的相关信息。

如果是从一个活动记录中获取信息,那么参数 ar 必须是之前使用lua_getstack填充或是作为 hook 的参数给出(参见lua_Hook)的有效活动记录。索引 n 决定了要查看哪个局部变量;关于局部变量索引和名称的细节,请参见debug.getlocal

lua_getlocal 会将变量的值压入栈中并返回其名称。

如果是从一个函数中获取信息,那么参数 ar 必须为NULL并且将要观察的函数放在栈顶上。这种情况下,只有Lua函数中的参数可见(因为没有关于活动变量的信息)并且不会将任何值压入栈中。

lua_getstack

[-0, +0, -]

int lua_getstack (lua_State *L, int level, lua_Debug *ar);

获取解释器运行时栈的相关信息。

此函数会根据某个活动记录 activation record 的标识来填充结构体lua_Debug的部分字段,这个活动记录来自于执行到给定层级 level 处的函数。当前运行的函数为第0层,而第 n+1 层的函数就是某个通过层层调用,调用了n层才到当前函数(尾调用不计数,其不算在调用栈层数中)。当传入的 level 参数大于当前调用栈的深度时,lua_getstack会返回0;否则返回1。

lua_getupvalue

[-0, +(0|1), -]

const char *lua_getupvalue (lua_State *L, int funcindex, int n);

获取给定索引处的闭包中的第 n 个上值。此函数会将对应上值的值压入栈中并返回它的名称。当索引 n 超出了实际上的上值数量时会返回 NULL(并且不会压栈)。

更多关于上值的细节请参见debug.getupvalue

lua_Hook

typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar);

调试函数 hook 的类型。

每当一个 hook 被调用,其参数 ar 的 event 字段都会被置为触发 hook 的特定事件。Lua使用这些常量来标识各种事件:LUA_HOOKCALL、LUA_HOOKRET、LUA_HOOKTAILCALL、LUA_HOOKLINE、LUA_HOOKCOUNT 。此外,在 line 事件中也会设置 currentline 字段。如要获取 ar 中的其他字段值,则必须调用lua_getinfo

对于 call 事件, event 可以是 LUA_HOOKCALL 表示常规调用;或是 LUA_HOOKTAILCALL 表示尾调用,此时不会有相应的 return 事件。

当Lua运行了一个 hook 时,它将屏蔽掉其他 hook 的调用。因此,当一个 hook 调用回了Lua并执行了某个函数或代码块,此时也不会触发其他的 hook 调用。

hook 函数没有延续函数,即不能在调用 lua_yieldklua_pcallklua_callk 使用非空的参数 k 。

hook 函数可以在这些条件下让出:只有 count 事件和 line 事件中可以让出;为了可以让出,hook 函数必须调用lua_yield完成执行,且参数 nresults 应当等于零(即没有返回值)。

lua_sethook

[-0, +0, -]

void lua_sethook (lua_State *L, lua_Hook f, int mask, int count);

设置调试用的 hook 函数。

参数 f 就是 hook 函数。mask 表示那些事件会触发 hook :其格式为各事件常量的按位或的结果,常量有 LUA_MASKCALL、LUA_MASKRET、LUA_MASKLINE、LUA_MASKCOUNT 。参数 count 只会在 mask 包括 LUA_MASKCOUNT 时才有意义。对于每个事件的 hook 调用的解释如下:

  • call hook: 当解释器调用一个函数时触发。这个 hook 只在Lua进入一个新函数后调用。
  • return hook: 当解释器从一个函数中返回时触发。这个 hook 只在Lua离开函数后调用。
  • line hook: 当解释器开始执行到代码新的一行时触发,或在其跳转回代码中时也会触发(即使是跳转到相同地方的代码)。这个事件只会在执行函数中触发。
  • count hook: 解释器每当执行了 count 条指令后触发。这个事件只会在执行函数中触发。

可以将 mask 置零以禁用 hook 。

lua_setlocal

[-(0|1), +0, -]

const char *lua_setlocal (lua_State *L, const lua_Debug *ar, int n);

设置给定的活动记录中的局部变量的值。此函数会将栈顶上的值赋给这个局部变量并返回变量名。同时也会将栈顶上的值弹出。

当给出的索引大于实际上正活跃的变量数量时会返回NULL(而且不会将任何值从栈上弹出)。

参数 ar 和 n 和在lua_getlocal中的相同。

lua_setupvalue

[-(0|1), +0, -]

const char *lua_setupvalue (lua_State *L, int funcindex, int n);

设置一个闭包的上值。此函数会将栈顶上的值赋给这个上值并返回其名称。同时也会将栈顶上的值弹出。

当给出的索引大于上值数量时会返回NULL(而且不会将任何值从栈上弹出)。

参数 funcindex 和 n 和在lua_getupvalue中的相同。

lua_upvalueid

[-0, +0, -]

void *lua_upvalueid (lua_State *L, int funcindex, int n);

返回给定索引 funcindex 处的闭包中的第 n 个上值的唯一标识。

此唯一标识可以用于区分不同的闭包之间是否共享了同一个上值。对于共享了上值的闭包(即访问了同样的外部局部变量),其返回共享上值的唯一标识也是相同的。

参数 funcindex 和 n 和在lua_getupvalue中的相同,但是 n 不可以大于上值的数量。

lua_upvaluejoin

[-0, +0, -]

void lua_upvaluejoin (lua_State *L, int funcindex1, int n1,
                                    int funcindex2, int n2);

使索引 funcindex1 处的闭包中第 n1 个上值引用索引 funcindex2 处的闭包中第 n2 个上值。即之前提到的闭包之间共享上值。