Stack Traces
Модуль StackTraces
предоставляет простые трассировки стека, которые легко читаемы для человека и просты в использовании программно.
Viewing a stack trace
Основная функция, используемая для получения трассировки стека, это 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
s. Для удобства можно использовать псевдоним 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
. При вызове 4d61726b646f776e2e436f64652822222c2022737461636b747261636528292229_40726566
из REPL у вас также будет несколько дополнительных фреймов в стеке от 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
Extracting useful information
Каждый StackTraces.StackFrame
содержит имя функции, имя файла, номер строки, информацию о лямбде, флаг, указывающий, был ли кадр встроен, флаг, указывающий, является ли это 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
Это делает информацию о трассировке стека доступной программно для ведения журналов, обработки ошибок и многого другого.
Error handling
Хотя легкий доступ к информации о текущем состоянии стека вызовов может быть полезен во многих местах, самым очевидным применением является обработка ошибок и отладка.
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
полностью отсутствует. Это вполне объяснимо, учитывая, что 4d61726b646f776e2e436f64652822222c2022737461636b74726163652229_40726566
вызывается в контексте catch. Хотя в этом примере довольно легко найти фактический источник ошибки, в сложных случаях отслеживание источника ошибки становится нетривиальным.
Это можно исправить, передав результат catch_backtrace
в stacktrace
. Вместо того чтобы возвращать информацию о стеке вызовов для текущего контекста, 4d61726b646f776e2e436f64652822222c202263617463685f6261636b74726163652229_40726566
возвращает информацию о стеке для контекста последнего исключения:
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
[...]
Exception stacks and 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)
может быть использован для проверки стека исключений для этой задачи.
Comparison with 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. Это происходит потому, что по умолчанию 4d61726b646f776e2e436f64652822222c2022737461636b74726163652229_40726566
удаляет любые функции 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.StackFrame
s, передавая их в StackTraces.lookup
:
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!