Трассировки стека
Просмотр трассировки стека
Основной функцией, используемой для получения трассировки стека, является 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})() в event.jl:92
При вызове функции stacktrace()
возвращается вектор типа StackTraces.StackFrame
. Для простоты вместо Vector{StackFrame}
можно использовать псевдоним StackTraces.StackTrace
. (Примеры с [...]
указывают, что вывод может изменяться в зависимости от того, как выполняется код.)
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
. При вызове функции stacktrace()
из 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})() в event.jl:92
Извлечение полезной информации
Каждый тип 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
Информацию о трассировке стека можно получить программным способом для ведения журнала, обработки ошибок и т. д.
Обработка ошибок
Хотя легкий доступ к информации о текущем состоянии стека вызовов может быть полезен во многих местах, наиболее очевидным его применением является обработка ошибок и отладка.
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 не ниже 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})() в 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})() в 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})() в 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})() в 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
путем их передачи функции 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!