Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

调试接口

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_HOOKCALLLUA_HOOKRETLUA_HOOKLINELUA_HOOKCOUNTLUA_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 字符含义:

字符获取信息
nnamenamewhat(函数名)
Ssourcesrclenlinedefinedlastlinedefinedwhatshort_src
lcurrentline
unupsnparamsisvararg
tistailcall
rftransferntransfer(尾调用传值信息)
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;
}