在lua pcall中获取错误的真实堆栈跟踪

时间:2017-08-21 02:55:26

标签: lua

所以对于我的pcall语句,我一直在做这样的事情

local status, err = pcall(fn)
if not status then
     print(err)
     print(debug.stacktrace())
end

这适用于一些基本的东西,但问题是debug.stacktrace()返回CURRENT相对堆栈跟踪,而不是错误的堆栈跟踪。如果fn中的错误在堆栈中发生了10级,那么我就不知道它究竟发生在哪里,只是这个pcall块失败了。我想知道是否有办法获得pcall的堆栈跟踪而不是当前的堆栈跟踪。我试过debug.stacktrace(err),但没有什么区别。

2 个答案:

答案 0 :(得分:4)

您需要使用xpcall来提供自定义函数,以便将堆栈跟踪添加到错误消息中。 From PiL

  

通常,当发生错误时,我们需要更多的调试信息   仅发生错误的位置。至少,我们想要一个   回溯,显示导致错误的完整堆栈调用。   当pcall返回其错误消息时,它会破坏堆栈的一部分   (从它到错误点的部分)。因此,如果我们   想要追溯,我们必须在pcall返回之前构建它。要做到这一点,   Lua提供了xpcall函数。除了要调用的功能外,   它接收第二个参数,一个错误处理函数。的情况下   错误,Lua在堆栈展开之前调用错误处理程序,这样   它可以使用调试库来收集它想要的任何额外信息   关于错误。

您可能需要查看此patch that extends pcall to include stacktrace

正如评论中所建议的那样,您可以将local ok, res = xpcall(f, debug.traceback, args...)与Lua 5.2+或LuaJIT(打开Lua 5.2兼容性)一起使用,并使用上面提到的Lua 5.1补丁。

答案 1 :(得分:4)

基本问题是(大致)pcall必须展开堆栈才能达到错误处理代码。这提供了两种明显的方法来解决这个问题:在展开之前创建堆栈跟踪,或者将(可能的)错误抛出代码移开,因此堆栈框架不必被删除。

第一个由xpcall处理。这将设置一个错误处理程序,可以在堆栈仍然完好时创建消息。 (请注意,在某些情况下xpcall不会调用处理程序, 1 因此它不适用于清理代码!但对于堆栈跟踪,它通常是足够好了。)

第二个选项(这总是 2 )是通过将代码移动到不同的协程来保留堆栈。而不是

local ok, r1, r2, etc = pcall( f, ... )

DO

local co = coroutine.create( f )
local ok, r1, r2, etc = coroutine.resume( f, ... )

现在堆栈(co)仍然保留,可由debug.traceback( co )或其他debug函数查询。

如果你想要完整的堆栈跟踪,那么你必须收集协同程序内部的堆栈跟踪和它外面的堆栈跟踪(你当前所在的位置),然后在删除第一行的同时将两者结合起来后者:

local full_tb = debug.traceback( co )
             .. debug.traceback( ):sub( 17 ) -- drop 'stack traceback:' line

1 没有调用处理程序的一种情况是OOM:

g = ("a"):rep( 1024*1024*1024 ) -- a gigabyte of 'a's
-- fail() tries to create a 32GB string – make it larger if that doesn't OOM
fail = load( "return "..("g"):rep( 32, ".." ), "(replicator)" )

-- plain call errors without traceback
fail()
--> not enough memory

-- xpcall does not call the handler either:
xpcall( fail, function(...) print( "handler:", ... ) return ... end, "foo" )
--> false   not enough memory

-- (for comparison: here, the handler is called)
xpcall( error, function(...) print( "handler:", ... ) return ... end, "foo" )
--> handler: foo
--  false   foo

-- coroutine preserves the stack anyway:
do
   local co = coroutine.create( fail )
   print( "result:", coroutine.resume( fail ) )
   print( debug.traceback( co ) .. debug.traceback( ):sub( 17 ) )
end
--> result: false   not enough memory
--> stack traceback:
--    [string "(replicator)"]:1: in function 'fail'
--    stdin:4: in main chunk
--    [C]: in ?

2 好吧,至少只要Lua本身不会崩溃。