调试接口
Lua 提供了一套完整的 C 级别调试 API,允许宿主程序检查调用栈、设置钩子(hook)、操作局部变量和 upvalue。
Hook 机制
通过 lua_sethook 可以在特定事件发生时执行回调:
void lua_sethook(lua_State *L, lua_Hook f, int mask, int count);
| 事件掩码 | 触发时机 |
|---|---|
LUA_MASKCALL | 调用函数时 |
LUA_MASKRET | 函数返回时 |
LUA_MASKLINE | 执行新行时 |
LUA_MASKCOUNT | 每执行 count 条指令时 |
Hook 函数签名:
typedef void (*lua_Hook)(lua_State *L, lua_Debug *ar);
ar->event
表示当前事件类型:LUA_HOOKCALL、LUA_HOOKRET、LUA_HOOKLINE、
LUA_HOOKCOUNT、LUA_HOOKTAILCALL。
在 hook 中通常只能做安全检查、计数或轻量信息收集,不应执行复杂 Lua 操作。
获取调用栈信息
int lua_getstack(lua_State *L, int level, lua_Debug *ar);
int lua_getinfo(lua_State *L, const char *what, lua_Debug *ar);
lua_getstack填充ar中的i_ci(CallInfo 指针)lua_getinfo根据what字符串查询详细信息
what 字符含义:
| 字符 | 获取信息 |
|---|---|
n | name 和 namewhat(函数名) |
S | source、srclen、linedefined、lastlinedefined、what、short_src |
l | currentline |
u | nups、nparams、isvararg |
t | istailcall |
r | ftransfer、ntransfer(尾调用传值信息) |
L | 将活跃行号压入栈(table) |
f | 将正在运行的函数压入栈 |
Lua 函数的 Upvalue 操作
这与第 11 章的 C 闭包 upvalue 不同,这里操作的是 Lua 函数的 upvalue:
const char *lua_getupvalue(lua_State *L, int funcindex, int n);
const char *lua_setupvalue(lua_State *L, int funcindex, int n);
funcindex指向栈上的 Lua 函数n是 upvalue 索引(从 1 开始)- 返回 upvalue 名称
lua_setupvalue会弹出栈顶的值并将其赋给指定 upvalue,调用前需先把新值压入栈
辅助库:luaL_traceback
void luaL_traceback(lua_State *L, lua_State *L1,
const char *msg, int level);
将 L1 的调用栈回溯信息压入 L 的栈。这是生成错误堆栈最便捷的方式。
完整代码
#include <assert.h>
#include <lauxlib.h>
#include <lua.h>
#include <lualib.h>
#include <stdio.h>
#include <string.h>
static int hook_calls = 0;
static int hook_lines = 0;
static void my_hook(lua_State *L, lua_Debug *ar) {
(void)L;
if (ar->event == LUA_HOOKCALL)
hook_calls++;
else if (ar->event == LUA_HOOKLINE)
hook_lines++;
}
static int traceback_msgh(lua_State *L) {
luaL_traceback(L, L, lua_tostring(L, 1), 1);
return 1;
}
int main(void) {
lua_State *L = luaL_newstate();
luaL_openlibs(L);
lua_sethook(L, my_hook, LUA_MASKCALL | LUA_MASKLINE, 0);
luaL_dostring(L, "function fact(n)\n"
" if n <= 1 then return 1 end\n"
" return n * fact(n - 1)\n"
"end\n"
"return fact(5)\n");
assert(hook_calls > 0);
assert(hook_lines > 0);
lua_pop(L, 1);
lua_sethook(L, NULL, 0, 0);
lua_getglobal(L, "fact");
lua_Debug ar;
assert(lua_getinfo(L, ">nuS", &ar) == 1);
assert(ar.linedefined == 1);
assert(ar.nups == 1);
assert(ar.nparams == 1);
assert(ar.isvararg == 0);
lua_pushcfunction(L, traceback_msgh);
luaL_loadstring(L, "function a() b() end\n"
"function b() c() end\n"
"function c() error('boom') end\n"
"a()\n");
assert(lua_pcall(L, 0, 0, -2) != LUA_OK);
assert(strstr(lua_tostring(L, -1), "boom") != NULL);
lua_pop(L, 1);
lua_pop(L, 1);
luaL_dostring(L, "local x = 10\n"
"local f = function() return x end\n"
"return f\n");
const char *name = lua_getupvalue(L, -1, 1);
assert(name != NULL);
assert(strcmp(name, "x") == 0);
assert(lua_tointeger(L, -1) == 10);
lua_pop(L, 1);
lua_pushinteger(L, 99);
name = lua_setupvalue(L, -2, 1);
assert(name != NULL);
lua_pcall(L, 0, 1, 0);
assert(lua_tointeger(L, -1) == 99);
lua_pop(L, 1);
if (lua_gettop(L) > 0 && lua_isfunction(L, -1))
lua_pop(L, 1);
lua_close(L);
puts("17-debug: ok");
return 0;
}