Стек-трейсы
Модуль StackTraces предоставляет простые стек-трейсы, которые легко читать и использовать программно.
Просмотр стек-трейса
Основная функция для получения стек-трейса — stacktrace():
julia> stacktrace()
3-element Array{StackFrame,1}:
eval at boot.jl:265
[inlined code from REPL.jl:3] eval_user_input at REPL.jl:62
[inlined code from REPL.jl:92] anonymous at task.jl:63
Вызов stacktrace() возвращает вектор StackFrame элементов. Для удобства используется псевдоним StackTrace вместо Vector{StackFrame}. (Примеры с ... показывают, что вывод может различаться в зависимости от способа выполнения кода.)
julia> example() = stacktrace()
example (generic function with 1 method)
julia> example()
6-element Array{StackFrame,1}:
in example() at none:1
in eval(::Module, ::Any) at boot.jl:234
in eval_user_input(::Any, ::Bool) at client.jl:117
in eval(::Module, ::Any) at boot.jl:234
in eval_user_input(::Any, ::Bool) at client.jl:117
in _start() at client.jl:355
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()
8-element Array{StackFrame,1}:
in child() at none:1
in parent() at none:1
in grandparent() at none:1
...
Обратите внимание, что при вызове stacktrace() вы обычно увидите фрейм с eval(...) at boot.jl. При вызове stacktrace() из REPL у вас также будет несколько дополнительных фреймов стека из REPL.jl, обычно выглядящих примерно так:
julia> example() = stacktrace()
example (generic function with 1 method)
julia> example()
5-element Array{StackFrame,1}:
in example() at REPL[1]:1
in eval(::Module, ::Any) at boot.jl:234
in eval_user_input(::Any, ::Base.REPL.REPLBackend) at REPL.jl:62
in macro expansion at REPL.jl:92 [inlined]
in (::Base.REPL.##1#2{Base.REPL.REPLBackend})() at event.jl:46
Извлечение полезной информации
Каждый StackFrame содержит имя функции, имя файла, номер строки, информацию о лямбда-выражении, флаг, указывающий, был ли фрейм инлайнирован, флаг, указывающий, является ли это C-функция (по умолчанию C-функции не отображаются в стек-трейсе), и целое число, представляющее указатель, возвращённый backtrace():
julia> top_frame = stacktrace()[1]
in eval(::Module, ::Any) at boot.jl:234
julia> top_frame.func
:eval
julia> top_frame.file
Symbol("./boot.jl")
julia> top_frame.line
234
julia> top_frame.linfo
Nullable{LambdaInfo}(LambdaInfo for eval(::Module, ::Any))
julia> top_frame.inlined
false
julia> top_frame.from_c
false
julia> top_frame.pointer 13203085684
Это позволяет получать информацию о стек-трейсе программно для ведения журнала, обработки ошибок и других целей.
Обработка ошибок
Легкий доступ к информации о текущем состоянии стека вызовов полезен во многих местах, но наиболее очевидное применение — обработка ошибок и отладка.
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()
6-element Array{StackFrame,1}:
in example() at none:4
in eval(::Module, ::Any) at boot.jl:234
...
Вы можете заметить, что в приведённом выше примере первый фрейм стека указывает на строку 4, где вызывается stacktrace(), а не на строку 2, где вызывается bad_function, и фрейм bad_function отсутствует. Это объяснимо, так как stacktrace() вызывается из контекста catch. Хотя в данном примере найти фактическую причину ошибки довольно легко, в сложных случаях отслеживание источника ошибки становится нетривиальной задачей.
Это можно исправить, вызвав catch_stacktrace() вместо stacktrace(). Вместо возвращения информации о стеке вызовов для текущего контекста, catch_stacktrace() возвращает информацию о стеке для контекста последней возникшей ошибки:
julia> @noinline bad_function() = undeclared_variable
bad_function (generic function with 1 method)
julia> @noinline example() = try
bad_function()
catch
catch_stacktrace()
end
example (generic function with 1 method)
julia> example()
7-element Array{StackFrame,1}:
in bad_function() at none:1
in example() at none: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)
catch_stacktrace()
end
end
grandparent (generic function with 1 method)
julia> grandparent()
ERROR: Whoops!
8-element Array{StackFrame,1}:
in child() at none:1
in parent() at none:1
in grandparent() at none:3
...
Сравнение с backtrace()
Вызов backtrace() возвращает вектор Ptr{Void}, который затем можно передать в stacktrace() для преобразования:
julia> trace = backtrace()
20-element Array{Ptr{Void},1}:
Ptr{Void} @0x0000000100a26fc2
Ptr{Void} @0x00000001029435df
Ptr{Void} @0x0000000102943635
Ptr{Void} @0x00000001009e9620
Ptr{Void} @0x00000001009fe1e8
Ptr{Void} @0x00000001009fc7b6
Ptr{Void} @0x00000001009fdae3
Ptr{Void} @0x00000001009fe0d2
Ptr{Void} @0x0000000100a1321b
Ptr{Void} @0x00000001009f64e7
Ptr{Void} @0x000000010265ac5d
Ptr{Void} @0x000000010265acc1
Ptr{Void} @0x00000001009e9620
Ptr{Void} @0x000000031007744b
Ptr{Void} @0x0000000310077537
Ptr{Void} @0x00000001009e9620
Ptr{Void} @0x000000031006feec
Ptr{Void} @0x00000003100701b0
Ptr{Void} @0x00000001009e9635
Ptr{Void} @0x0000000100a06418
julia> stacktrace(trace)
5-element Array{StackFrame,1}:
in backtrace() at error.jl:26
in eval(::Module, ::Any) at boot.jl:231
in eval_user_input(::Any, ::Base.REPL.REPLBackend) at REPL.jl:62
in macro expansion at REPL.jl:92 [inlined]
in (::Base.REPL.##1#2{Base.REPL.REPLBackend})() at event.jl:46
Обратите внимание, что вектор, возвращённый backtrace(), содержал 15 указателей, в то время как вектор, возвращённый stacktrace(), содержит только 4. Это происходит потому, что по умолчанию stacktrace() удаляет все фреймы C-функций из стека. Если вам нужно включить фреймы стека из вызовов C, можно сделать это так:
julia> stacktrace(trace, true)
26-element Array{StackFrame,1}:
in jl_backtrace_from_here at stackwalk.c:104
in backtrace() at error.jl:26
in ip:0x102943635
in jl_call_method_internal at julia_internal.h:86 [inlined]
in jl_apply_generic at gf.c:1805
in do_call at interpreter.c:65
in eval at interpreter.c:188
in eval_body at interpreter.c:469
in jl_interpret_call at interpreter.c:573
in jl_toplevel_eval_flex at toplevel.c:543
in jl_toplevel_eval_in_warn at builtins.c:571
in eval(::Module, ::Any) at boot.jl:231
in ip:0x10265acc1
in jl_call_method_internal at julia_internal.h:86 [inlined]
in jl_apply_generic at gf.c:1805
in eval_user_input(::Any, ::Base.REPL.REPLBackend) at REPL.jl:62
in ip:0x310077537
in jl_call_method_internal at julia_internal.h:86 [inlined]
in jl_apply_generic at gf.c:1805
in macro expansion at REPL.jl:92 [inlined]
in (::Base.REPL.##1#2{Base.REPL.REPLBackend})() at event.jl:46
in ip:0x3100701b0
in jl_call_method_internal at julia_internal.h:86 [inlined]
in jl_apply_generic at gf.c:1795
in jl_apply at julia.h:1388 [inlined]
in start_task at task.c:247
Индивидуальные указатели, возвращаемые backtrace(), можно преобразовать в StackFrame элементы, передав их в StackTraces.lookup():
julia> pointer = backtrace()[1];
julia> frame = StackTraces.lookup(pointer)
1-element Array{StackFrame,1}:
in jl_backtrace_from_here at stackwalk.c:105
julia> println("The top frame is from $(frame[1].func)!")
The top frame is from jl_backtrace_from_here!
© 2009–2016 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/release-0.5/manual/stacktraces/