获取协程的完整堆栈跟踪,包括恢复协程的位置

问题描述

一个简单的例子:

coroutine.resume(coroutine.create(function()
    print(debug.traceback())
end))
print(debug.traceback())

输出

stack traceback:
        ./v.lua:2: in function <./v.lua:1>
stack traceback:
        ./v.lua:4: in main chunk
        [C]: in ?

它表明协程中的 traceback 不知道它是如何恢复的,因此 xxx: in main chunk 不显示

如何在协程中获取完整的堆栈跟踪?

解决方法

好吧,我在这里找到了解决方法。

由于一个 Lua VM 一次只有一个执行点(这就是为什么那里有一个完整的调用堆栈),我们可以手动记录恢复信息。

在 lua 中,手动构建恢复堆栈跟踪。

local xresume = coroutine.resume
local xtrace = debug.traceback

-- magic here! Take care of main thread.
local mainthr = coroutine.running()      -- captureing is a must.
debug.traceback = function(athr)
  if athr then return xtrace(athr) end  -- no interest in specified thread.
  return xtrace(mainthr)
end

coroutine.resume = function(thr,...)
  -- another magic.
  local uptrace = debug.traceback
  debug.traceback = function(athr)
    if athr then return xtrace(athr) end  -- no interest in specified thread.
    return xtrace(thr)     -- trace the stack of thr.
      .. '\n' .. uptrace() -- trace from thr's resume point.
  end
  
  local result = { xresume(thr,...) }
  debug.traceback = uptrace
  return table.unpack(result)
end

其他提示:

  • 使用全局表来存储线程也有效。但是你还是需要捕获主线程,这样才能到处追踪。

  • 在 C 函数中编写代码可以防止回溯到钩子 coroutine.resumedebug.traceback 本身,为您提供更清晰的输出。

  • 如果不调用 debug.traceback,您的性能不会受到太大影响。