【问题标题】:How to get current function call stack depth in Lua?如何在 Lua 中获取当前函数调用堆栈深度?
【发布时间】:2018-08-09 16:21:41
【问题描述】:

我想有效地获取当前的函数调用堆栈,是否有 api 或某事可以做到这一点?

我试过这样的方法,但是太慢了。

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 版本。

【问题讨论】:

  • 什么是“太慢”?
  • 你真正想解决什么问题?

标签: lua


【解决方案1】:

对于尾调用,您会获得不同的事件(或在激活记录中获得 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” (truefalse) 的子节点已经存在,函数 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 返回的信息堆栈深度。

【讨论】:

    猜你喜欢
    • 2016-03-10
    • 2017-09-16
    • 2014-04-26
    • 2019-09-24
    • 1970-01-01
    • 2015-11-28
    • 1970-01-01
    • 2010-11-07
    相关资源
    最近更新 更多