如何在Lua中获取当前函数调用堆栈深度?

时间:2018-04-23 10:37:27

标签: lua

我想有效地获取当前的函数调用堆栈,是否有api或sth来执行此操作?

我尝试过这样的方法,但速度太慢了。

local function GetStackDepth()
    local depth = 0
    while true do
        if not debug.getinfo(3 + depth) then
            break
        end
        depth = depth + 1
    end
    return depth
end

编辑: 真正的问题是我正在编写一个分析器工具,并在调用和返回事件中使用debug.sethook做某事。但是在lua5.1或lua-jit情况下,当尾部返回发生时,我得到两个调用事件,只有一个这样的返回事件:

call     ------ the 1st call event
call
return

所以我对这个问题的解决方案是获取当前事件的调用堆栈深度,当返回事件深度小于第一个调用事件的深度时,我知道它的尾部返回,然后我可以正确处理它。

但我发现GetStackDepth()自我花费了很多时间(太慢),这会影响我的探查器结果。

  

我无法改变lua版本。

1 个答案:

答案 0 :(得分:0)

对于尾部调用,您将获得另一个事件(或在激活记录中获得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具有Node(一个根节点,另外的根节点被添加为子节点)并跟踪curNode。 (实际上,我也构建了函数信息,但是为了简化起见,我删除了该代码。显然,节点也可以存储各种其他信息。)
如果相同功能指针(Node::addCall),行(luaPtr)和“ tailcall-ness”(ar->currentline或{的子节点相同,则函数true仅增加一个数字{1}}已存在,否则将创建新的false。在这两种情况下,都将返回相关的子节点并将其用作新的currentNode。

Node

您会注意到,在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

不幸的是,尽管这可以正确处理尾部调用,但我目前的问题是在错误处理下,调用和返回之间仍然存在不匹配,因此我希望能够直接向lua询问调用堆栈的深度,而不是必须进行缓慢的“反复试验”。
我当前的计划(我尚未实施)是跟踪我预期的通话深度,然后获取该级别的信息。如果那是我所期望的,那就太好了,不需要进一步的测试,不需要花费适中的费用,否则我会沿着堆栈向上(或向下走?),直到到达存储的调用图信息适合lua返回的信息为止。堆积深度。