获取 lua pcall 中的错误真实堆栈信息

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

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

这对于一些基本的东西很好用,但问题是 debug.stacktrace() 返回的是当前的相对堆栈跟踪,而不是错误的堆栈跟踪。如果 fn 中的错误发生在堆栈的第 10 层,那么我就不知道它发生在哪里,只知道这个 pcall 块失败了。我想知道是否有一种方法可以获取 pcall 的堆栈跟踪而不是当前的堆栈跟踪。我尝试过 debug.stacktrace(err),但没有改变任何事情。

点赞
用户1442917
用户1442917

你需要使用 xpcall 函数来提供一个自定义函数,将调用栈信息添加到错误消息中。来自《Lua 程序设计》的内容如下:

经常在出现错误时,我们需要的不仅仅是错误发生的位置信息,我们还需要更多的调试信息。至少,我们需要回溯信息,显示导致错误的完整调用栈。当 pcall 返回错误信息时,它会销毁调用栈的一部分(从 pcall 到错误点的部分)。因此,如果我们想要回溯信息,我们必须在 pcall 返回之前构建它。为此,Lua 提供了 xpcall 函数。除了要调用的函数之外,它还接收第二个参数:一个错误处理函数。在出现错误时,Lua 会在取消栈展开前调用该错误处理函数,这样它就可以使用调试库收集有关错误的任何额外信息了。

你可以查看这个 扩展 pcall 函数以包含调用栈信息的补丁

如评论中所建议的,你可以在 Lua 5.2+ 或者开启了与 Lua 5.2 兼容性的 LuaJIT 中使用方式为 local ok, res = xpcall(f, debug.traceback, args...),并在 Lua 5.1 中使用上面提到的补丁。

2017-08-21 03:38:33
用户805875
用户805875

基本的问题是(大致上)pcall必须展开栈,以便到达您的错误处理代码。这提供了两种解决问题的明显方法:在展开之前创建堆栈跟踪,或将(潜在的)引发错误的代码移开,以便不必删除堆栈帧。

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

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

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

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在OOMs中不调用处理程序的一种情况是:

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本身不崩溃的情况下。

2017-08-21 04:32:49