Стек-трейсы
Модуль StackTraces предоставляет простые стек-трейсы, которые легко читаются человеком и удобны для использования в программах.
Просмотр стек-трейса
Основная функция для получения стек-трейса — stacktrace():
julia> stacktrace()
4-element Array{StackFrame,1}:
eval(::Module, ::Any) at boot.jl:236
eval_user_input(::Any, ::Base.REPL.REPLBackend) at REPL.jl:66
macro expansion at REPL.jl:97 [inlined]
(::Base.REPL.##1#2{Base.REPL.REPLBackend})() at event.jl:73
Вызов stacktrace() возвращает вектор StackFrame . Для удобства использования можно использовать псевдоним StackTrace вместо Vector{StackFrame}. (Примеры с [...] показывают, что вывод может отличаться в зависимости от того, как выполняется код.)
julia> example() = stacktrace()
example (generic function with 1 method)
julia> example()
5-element Array{StackFrame,1}:
example() at REPL[1]:1
eval(::Module, ::Any) at boot.jl:236
[...]
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()
7-element Array{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()
5-element Array{StackFrame,1}:
example() at REPL[1]:1
eval(::Module, ::Any) at boot.jl:236
eval_user_input(::Any, ::Base.REPL.REPLBackend) at REPL.jl:66
macro expansion at REPL.jl:97 [inlined]
(::Base.REPL.##1#2{Base.REPL.REPLBackend})() at event.jl:73
Извлечение полезной информации
Каждый StackFrame содержит имя функции, имя файла, номер строки, информацию о лямбда-выражении, флаг, указывающий, был ли фрейм вставлен, флаг, указывающий, является ли это функция C (по умолчанию функции C не отображаются в стек-трейсе), и целочисленное представление указателя, возвращённого backtrace():
julia> top_frame = stacktrace()[1]
eval(::Module, ::Any) at boot.jl:236
julia> top_frame.func
:eval
julia> top_frame.file
Symbol("./boot.jl")
julia> top_frame.line
236
julia> top_frame.linfo
Nullable{Core.MethodInstance}(MethodInstance for eval(::Module, ::Any))
julia> top_frame.inlined
false
julia> top_frame.from_c
false
julia> top_frame.pointer 0x00007f390d152a59
Это позволяет получить информацию о стек-трейсе программно для ведения журнала, обработки ошибок и других задач.
Обработка ошибок
Хотя лёгкий доступ к информации о текущем состоянии стека вызовов может быть полезен во многих случаях, наиболее очевидное применение — в обработке ошибок и отладке.
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()
5-element Array{StackFrame,1}:
example() at REPL[2]:4
eval(::Module, ::Any) at boot.jl:236
[...]
Вы можете заметить, что в приведённом выше примере первый фрейм стека указывает на строку 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()
6-element Array{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)
catch_stacktrace()
end
end
grandparent (generic function with 1 method)
julia> grandparent()
ERROR: Whoops!
7-element Array{StackFrame,1}:
child() at REPL[1]:1
parent() at REPL[2]:1
grandparent() at REPL[3]:3
[...]
Сравнение с backtrace()
Вызов backtrace() возвращает вектор Ptr{Void}, который затем можно передать в stacktrace() для перевода:
julia> trace = backtrace()
21-element Array{Ptr{Void},1}:
Ptr{Void} @0x00007f10049d5b2f
Ptr{Void} @0x00007f0ffeb4d29c
Ptr{Void} @0x00007f0ffeb4d2a9
Ptr{Void} @0x00007f1004993fe7
Ptr{Void} @0x00007f10049a92be
Ptr{Void} @0x00007f10049a823a
Ptr{Void} @0x00007f10049a9fb0
Ptr{Void} @0x00007f10049aa718
Ptr{Void} @0x00007f10049c0d5e
Ptr{Void} @0x00007f10049a3286
Ptr{Void} @0x00007f0ffe9ba3ba
Ptr{Void} @0x00007f0ffe9ba3d0
Ptr{Void} @0x00007f1004993fe7
Ptr{Void} @0x00007f0ded34583d
Ptr{Void} @0x00007f0ded345a87
Ptr{Void} @0x00007f1004993fe7
Ptr{Void} @0x00007f0ded34308f
Ptr{Void} @0x00007f0ded343320
Ptr{Void} @0x00007f1004993fe7
Ptr{Void} @0x00007f10049aeb67
Ptr{Void} @0x0000000000000000
julia> stacktrace(trace)
5-element Array{StackFrame,1}:
backtrace() at error.jl:46
eval(::Module, ::Any) at boot.jl:236
eval_user_input(::Any, ::Base.REPL.REPLBackend) at REPL.jl:66
macro expansion at REPL.jl:97 [inlined]
(::Base.REPL.##1#2{Base.REPL.REPLBackend})() at event.jl:73
Обратите внимание, что вектор, возвращаемый backtrace(), содержал 21 указатель, а вектор, возвращаемый stacktrace(), содержит только 5. Это связано с тем, что по умолчанию stacktrace() удаляет из стека любые функции нижнего уровня C. Если вы хотите включить фреймы стека из вызовов C, можно сделать это так:
julia> stacktrace(trace, true)
27-element Array{StackFrame,1}:
jl_backtrace_from_here at stackwalk.c:103
backtrace() at error.jl:46
backtrace() at sys.so:?
jl_call_method_internal at julia_internal.h:248 [inlined]
jl_apply_generic at gf.c:2215
do_call at interpreter.c:75
eval at interpreter.c:215
eval_body at interpreter.c:519
jl_interpret_toplevel_thunk at interpreter.c:664
jl_toplevel_eval_flex at toplevel.c:592
jl_toplevel_eval_in at builtins.c:614
eval(::Module, ::Any) at boot.jl:236
eval(::Module, ::Any) at sys.so:?
jl_call_method_internal at julia_internal.h:248 [inlined]
jl_apply_generic at gf.c:2215
eval_user_input(::Any, ::Base.REPL.REPLBackend) at REPL.jl:66
ip:0x7f1c707f1846
jl_call_method_internal at julia_internal.h:248 [inlined]
jl_apply_generic at gf.c:2215
macro expansion at REPL.jl:97 [inlined]
(::Base.REPL.##1#2{Base.REPL.REPLBackend})() at event.jl:73
ip:0x7f1c707ea1ef
jl_call_method_internal at julia_internal.h:248 [inlined]
jl_apply_generic at gf.c:2215
jl_apply at julia.h:1411 [inlined]
start_task at task.c:261
ip:0xffffffffffffffff
Отдельные указатели, возвращаемые backtrace(), можно преобразовать в StackFrame, передав их в StackTraces.lookup():
julia> pointer = backtrace()[1];
julia> frame = StackTraces.lookup(pointer)
1-element Array{StackFrame,1}:
jl_backtrace_from_here at stackwalk.c:103
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.6/manual/stacktraces/