栈跟踪
StackTraces
模块提供简单易懂且易于以编程方式使用的栈跟踪。
查看栈跟踪
用于获取栈跟踪的主要函数是 stacktrace
6-element Array{Base.StackTraces.StackFrame,1}:
top-level scope
eval at boot.jl:317 [inlined]
eval(::Module, ::Expr) at REPL.jl:5
eval_user_input(::Any, ::REPL.REPLBackend) at REPL.jl:85
macro expansion at REPL.jl:116 [inlined]
(::getfield(REPL, Symbol("##28#29")){REPL.REPLBackend})() at event.jl:92
调用 stacktrace()
返回一个 StackTraces.StackFrame
的向量。为了方便使用,别名 StackTraces.StackTrace
可以代替 Vector{StackFrame}
使用。(带有 [...]
的示例表示输出可能因代码运行方式而异。)
julia> example() = stacktrace()
example (generic function with 1 method)
julia> example()
7-element Array{Base.StackTraces.StackFrame,1}:
example() at REPL[1]:1
top-level scope
eval at boot.jl:317 [inlined]
[...]
julia> @noinline child() = stacktrace()
child (generic function with 1 method)
julia> @noinline parent() = child()
parent (generic function with 1 method)
julia> grandparent() = parent()
grandparent (generic function with 1 method)
julia> grandparent()
9-element Array{Base.StackTraces.StackFrame,1}:
child() at REPL[3]:1
parent() at REPL[4]:1
grandparent() at REPL[5]:1
[...]
请注意,在调用 stacktrace()
时,您通常会看到一个带有 eval at boot.jl
的帧。从 REPL 调用 stacktrace()
时,栈中还会有几个额外的帧来自 REPL.jl
,通常看起来像这样
julia> example() = stacktrace()
example (generic function with 1 method)
julia> example()
7-element Array{Base.StackTraces.StackFrame,1}:
example() at REPL[1]:1
top-level scope
eval at boot.jl:317 [inlined]
eval(::Module, ::Expr) at REPL.jl:5
eval_user_input(::Any, ::REPL.REPLBackend) at REPL.jl:85
macro expansion at REPL.jl:116 [inlined]
(::getfield(REPL, Symbol("##28#29")){REPL.REPLBackend})() at event.jl:92
提取有用信息
每个 StackTraces.StackFrame
包含函数名、文件名、行号、lambda 信息、一个指示帧是否已内联的标志、一个指示它是否为 C 函数的标志(默认情况下,C 函数不会出现在栈跟踪中)以及由 backtrace
返回的指针的整数表示形式。
julia> frame = stacktrace()[3]
eval(::Module, ::Expr) at REPL.jl:5
julia> frame.func
:eval
julia> frame.file
Symbol("~/julia/usr/share/julia/stdlib/v0.7/REPL/src/REPL.jl")
julia> frame.line
5
julia> frame.linfo
MethodInstance for eval(::Module, ::Expr)
julia> frame.inlined
false
julia> frame.from_c
false
julia> frame.pointer
0x00007f92d6293171
这使得栈跟踪信息可用于编程方式的日志记录、错误处理等。
错误处理
虽然能够轻松访问有关调用栈当前状态的信息在许多地方都很有用,但最明显的应用是在错误处理和调试中。
julia> @noinline bad_function() = undeclared_variable
bad_function (generic function with 1 method)
julia> @noinline example() = try
bad_function()
catch
stacktrace()
end
example (generic function with 1 method)
julia> example()
7-element Array{Base.StackTraces.StackFrame,1}:
example() at REPL[2]:4
top-level scope
eval at boot.jl:317 [inlined]
[...]
您可能会注意到,在上面的示例中,第一个栈帧指向第 4 行,即调用 stacktrace
的位置,而不是第 2 行,即调用 bad_function 的位置,并且 bad_function
的帧完全丢失了。考虑到 stacktrace
是从 catch 的上下文中调用的,这是可以理解的。虽然在这个例子中很容易找到错误的实际来源,但在复杂的情况下,追踪错误的来源变得不那么容易。
可以通过将 catch_backtrace
的结果传递给 stacktrace
来解决此问题。catch_backtrace
不会返回当前上下文的调用栈信息,而是返回最近异常的上下文中的栈信息。
julia> @noinline bad_function() = undeclared_variable
bad_function (generic function with 1 method)
julia> @noinline example() = try
bad_function()
catch
stacktrace(catch_backtrace())
end
example (generic function with 1 method)
julia> example()
8-element Array{Base.StackTraces.StackFrame,1}:
bad_function() at REPL[1]:1
example() at REPL[2]:2
[...]
请注意,栈跟踪现在指示了正确的行号和丢失的帧。
julia> @noinline child() = error("Whoops!")
child (generic function with 1 method)
julia> @noinline parent() = child()
parent (generic function with 1 method)
julia> @noinline function grandparent()
try
parent()
catch err
println("ERROR: ", err.msg)
stacktrace(catch_backtrace())
end
end
grandparent (generic function with 1 method)
julia> grandparent()
ERROR: Whoops!
10-element Array{Base.StackTraces.StackFrame,1}:
error at error.jl:33 [inlined]
child() at REPL[1]:1
parent() at REPL[2]:1
grandparent() at REPL[3]:3
[...]
异常栈和 current_exceptions
异常栈需要至少 Julia 1.1。
在处理异常时,可能会抛出更多异常。检查所有这些异常以识别问题的根本原因可能很有用。Julia 运行时通过在异常发生时将其推送到内部 异常栈 来支持此功能。当代码正常退出 catch
时,任何在关联的 try
中推送到栈上的异常都被视为已成功处理并从栈中移除。
可以使用 current_exceptions
函数访问当前异常栈。例如,
julia> try
error("(A) The root cause")
catch
try
error("(B) An exception while handling the exception")
catch
for (exc, bt) in current_exceptions()
showerror(stdout, exc, bt)
println(stdout)
end
end
end
(A) The root cause
Stacktrace:
[1] error(::String) at error.jl:33
[2] top-level scope at REPL[7]:2
[3] eval(::Module, ::Any) at boot.jl:319
[4] eval_user_input(::Any, ::REPL.REPLBackend) at REPL.jl:85
[5] macro expansion at REPL.jl:117 [inlined]
[6] (::getfield(REPL, Symbol("##26#27")){REPL.REPLBackend})() at task.jl:259
(B) An exception while handling the exception
Stacktrace:
[1] error(::String) at error.jl:33
[2] top-level scope at REPL[7]:5
[3] eval(::Module, ::Any) at boot.jl:319
[4] eval_user_input(::Any, ::REPL.REPLBackend) at REPL.jl:85
[5] macro expansion at REPL.jl:117 [inlined]
[6] (::getfield(REPL, Symbol("##26#27")){REPL.REPLBackend})() at task.jl:259
在此示例中,根本原因异常 (A) 位于栈顶,后面跟着另一个异常 (B)。在正常退出两个 catch 块后(即,不抛出进一步的异常),所有异常都从栈中移除,并且不再可访问。
异常栈存储在发生异常的 Task
上。当任务因未捕获的异常而失败时,可以使用 current_exceptions(task)
检查该任务的异常栈。
与 backtrace
对 backtrace
的调用返回一个 Union{Ptr{Nothing}, Base.InterpreterIP}
的向量,然后可以将其传递给 stacktrace
进行转换。
julia> trace = backtrace()
18-element Array{Union{Ptr{Nothing}, Base.InterpreterIP},1}:
Ptr{Nothing} @0x00007fd8734c6209
Ptr{Nothing} @0x00007fd87362b342
Ptr{Nothing} @0x00007fd87362c136
Ptr{Nothing} @0x00007fd87362c986
Ptr{Nothing} @0x00007fd87362d089
Base.InterpreterIP(CodeInfo(:(begin
Core.SSAValue(0) = backtrace()
trace = Core.SSAValue(0)
return Core.SSAValue(0)
end)), 0x0000000000000000)
Ptr{Nothing} @0x00007fd87362e4cf
[...]
julia> stacktrace(trace)
6-element Array{Base.StackTraces.StackFrame,1}:
top-level scope
eval at boot.jl:317 [inlined]
eval(::Module, ::Expr) at REPL.jl:5
eval_user_input(::Any, ::REPL.REPLBackend) at REPL.jl:85
macro expansion at REPL.jl:116 [inlined]
(::getfield(REPL, Symbol("##28#29")){REPL.REPLBackend})() at event.jl:92
请注意,由 backtrace
返回的向量有 18 个元素,而由 stacktrace
返回的向量只有 6 个。这是因为,默认情况下,stacktrace
会从栈中移除任何较低级别的 C 函数。如果要包含来自 C 调用的栈帧,可以这样做
julia> stacktrace(trace, true)
21-element Array{Base.StackTraces.StackFrame,1}:
jl_apply_generic at gf.c:2167
do_call at interpreter.c:324
eval_value at interpreter.c:416
eval_body at interpreter.c:559
jl_interpret_toplevel_thunk_callback at interpreter.c:798
top-level scope
jl_interpret_toplevel_thunk at interpreter.c:807
jl_toplevel_eval_flex at toplevel.c:856
jl_toplevel_eval_in at builtins.c:624
eval at boot.jl:317 [inlined]
eval(::Module, ::Expr) at REPL.jl:5
jl_apply_generic at gf.c:2167
eval_user_input(::Any, ::REPL.REPLBackend) at REPL.jl:85
jl_apply_generic at gf.c:2167
macro expansion at REPL.jl:116 [inlined]
(::getfield(REPL, Symbol("##28#29")){REPL.REPLBackend})() at event.jl:92
jl_fptr_trampoline at gf.c:1838
jl_apply_generic at gf.c:2167
jl_apply at julia.h:1540 [inlined]
start_task at task.c:268
ip:0xffffffffffffffff
由 backtrace
返回的单个指针可以通过将其传递给 StackTraces.lookup
来转换为 StackTraces.StackFrame
。
julia> pointer = backtrace()[1];
julia> frame = StackTraces.lookup(pointer)
1-element Array{Base.StackTraces.StackFrame,1}:
jl_apply_generic at gf.c:2167
julia> println("The top frame is from $(frame[1].func)!")
The top frame is from jl_apply_generic!