对于尾调用,您会获得不同的事件(或在激活记录中获得 istailcall)。我不得不承认我正在使用 C API 进行分析代码,但将其转换为本地 lua 应该不会太棘手;尽管如果您有性能问题,请注意 Roberto Ierusalimschy 在Lua 编程,第 3 版,第 24 章“调试库”中写道:
出于性能原因,这些原语的官方接口是通过 C API。
我有一个主要的 C 风格调试钩子,它将调用转发到事件处理程序:
void debugHook(lua_State * const ls, lua_Debug * const ar)
{
ASSERT(ls != nullptr);
ASSERT(ar != nullptr);
CallGraphTracker & dbg = getCallGraphTracker();
switch(ar->event)
{
case LUA_HOOKCALL:
dbg.handleEventCall(ls, ar);
break;
case LUA_HOOKTAILCALL:
dbg.handleEventTailCall(ls, ar);
break;
case LUA_HOOKRET:
dbg.handleEventReturn(ls, ar);
break;
default:
SHOULD_NEVER_BE_REACHED();
}
}
在我的例子中,事件处理程序基于 lua 函数指针构建调用图; CallGraphTracker 有Nodes(一个根节点和更多的被添加为子节点)并跟踪curNode。 (实际上,我也构建了函数信息,但为了简单起见,我删除了该代码。显然,节点也可以存储各种附加信息。)
如果相同的函数指针 (luaPtr)、行 (ar->currentline) 和“tailcall-ness” (true 或 false) 的子节点已经存在,函数 Node::addCall 只会增加一个数字,否则新的Node 被创建。在这两种情况下,相关的子节点都会被返回并用作新的 currentNode。
void CallGraphTracker::handleEventCall(lua_State * ls, lua_Debug * ar)
{
// Get function info and transform into simple function pointer.
lua_getinfo(ls, "f", ar);
ASSERT(lua_isfunction(ls, -1));
void const * luaPtr = lua_topointer(ls, -1);
ASSERT(luaPtr != nullptr);
// Add call.
lua_getstack(ls, 1, ar);
lua_getinfo(ls, "l", ar);
curNode = curNode->addCall(luaPtr, ar->currentline, false);
}
void CallGraphTracker::handleEventTailCall(lua_State * ls, lua_Debug * ar)
{
// Get function info and transform into simple function pointer.
lua_getinfo(ls, "nSf", ar);
ASSERT(lua_isfunction(ls, -1));
void const * luaPtr = lua_topointer(ls, -1);
ASSERT(luaPtr != nullptr);
// Add tail call.
lua_getstack(ls, 1, ar);
lua_getinfo(ls, "l", ar);
curNode = curNode->addCall(luaPtr, ar->currentline, true);
}
void CallGraphTracker::handleEventReturn(lua_State *, lua_Debug *)
{
while(curNode->isTailCall())
{
ASSERT(curNode->getCallerNode() != nullptr);
curNode = curNode->getCallerNode();
}
ASSERT(curNode->getCallerNode() != nullptr);
curNode = curNode->getCallerNode();
}
您会注意到,在handleEventReturn 中,我首先遍历尾调用,然后进行“正确”返回。在表单的尾调用递归的情况下,您不能假设固定数量的尾调用(严格来说)
function f(num)
if num == 0 then
return
end
-- Do something
return f(num-1)
end
不幸的是,虽然这可以正确处理尾调用,但我目前的问题是在错误处理下调用和返回之间仍然可能存在不匹配,所以我希望能够直接向 lua 询问调用堆栈的深度,而不必进行缓慢的“反复试验”。
我目前的计划(我还没有实施)是跟踪我的预期呼叫深度,然后获取该级别的信息。如果这是我所期望的,那太好了,不需要进一步的测试,涉及的成本适中,否则我会向上(或者向下?)堆栈,直到我存储的调用图信息适合从 lua 返回的信息堆栈深度。