Документация Engee

Трассировки стека

Модуль 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})() в 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!