Stack Traces

Das StackTraces-Modul bietet einfache Stack-Traces, die sowohl für Menschen lesbar als auch programmgesteuert leicht zu verwenden sind.

Viewing a stack trace

Die primäre Funktion, die verwendet wird, um einen Stack-Trace zu erhalten, ist 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

Der Aufruf von stacktrace() gibt einen Vektor von StackTraces.StackFrame zurück. Zur Vereinfachung kann das Alias StackTraces.StackTrace anstelle von Vector{StackFrame} verwendet werden. (Beispiele mit [...] deuten darauf hin, dass die Ausgabe je nach Ausführung des Codes variieren kann.)

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
[...]

Beachten Sie, dass Sie beim Aufrufen von stacktrace() typischerweise einen Frame mit eval at boot.jl sehen. Wenn Sie 4d61726b646f776e2e436f64652822222c2022737461636b747261636528292229_40726566 aus dem REPL aufrufen, haben Sie auch einige zusätzliche Frames im Stack von REPL.jl, die normalerweise so aussehen:

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

Jede StackTraces.StackFrame enthält den Funktionsnamen, den Dateinamen, die Zeilennummer, die Lambda-Informationen, ein Flag, das angibt, ob der Frame inline ist, ein Flag, das angibt, ob es sich um eine C-Funktion handelt (standardmäßig erscheinen C-Funktionen nicht im Stack-Trace), und eine ganzzahlige Darstellung des von backtrace zurückgegebenen Zeigers:

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

Dies macht Stack-Trace-Informationen programmgesteuert für Protokollierung, Fehlerbehandlung und mehr verfügbar.

Error handling

Während der einfache Zugang zu Informationen über den aktuellen Zustand des Callstacks in vielen Bereichen hilfreich sein kann, ist die offensichtlichste Anwendung die Fehlerbehandlung und das Debugging.

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]
[...]

Sie werden möglicherweise feststellen, dass im obigen Beispiel der erste Stack-Frame auf Zeile 4 zeigt, wo stacktrace aufgerufen wird, anstatt auf Zeile 2, wo bad_function aufgerufen wird, und der Frame von bad_function vollständig fehlt. Dies ist verständlich, da 4d61726b646f776e2e436f64652822222c2022737461636b74726163652229_40726566 im Kontext des catch aufgerufen wird. Während es in diesem Beispiel recht einfach ist, die tatsächliche Quelle des Fehlers zu finden, wird es in komplexen Fällen nicht trivial, die Quelle des Fehlers zu verfolgen.

Dies kann behoben werden, indem das Ergebnis von catch_backtrace an stacktrace übergeben wird. Anstatt Informationen zum Aufrufstapel für den aktuellen Kontext zurückzugeben, gibt 4d61726b646f776e2e436f64652822222c202263617463685f6261636b74726163652229_40726566 Informationen zum Stapel für den Kontext der letzten Ausnahme zurück:

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
[...]

Beachten Sie, dass der Stack-Trace jetzt die entsprechende Zeilennummer und den fehlenden Frame anzeigt.

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

Ausnahme-Stacks erfordern mindestens Julia 1.1.

Während der Behandlung einer Ausnahme können weitere Ausnahmen ausgelöst werden. Es kann nützlich sein, all diese Ausnahmen zu inspizieren, um die Grundursache eines Problems zu identifizieren. Die Julia-Laufzeit unterstützt dies, indem jede Ausnahme, sobald sie auftritt, auf einen internen Ausnahme-Stack geschoben wird. Wenn der Code normal aus einem catch austritt, werden alle Ausnahmen, die im zugehörigen try auf den Stack geschoben wurden, als erfolgreich behandelt betrachtet und vom Stack entfernt.

Der Stapel der aktuellen Ausnahmen kann mit der Funktion current_exceptions zugegriffen werden. Zum Beispiel,

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

In diesem Beispiel steht die Grundursache-Ausnahme (A) zuerst im Stack, gefolgt von einer weiteren Ausnahme (B). Nachdem beide Catch-Blöcke normal verlassen wurden (d.h. ohne eine weitere Ausnahme auszulösen), werden alle Ausnahmen aus dem Stack entfernt und sind nicht mehr zugänglich.

Der Ausnahme-Stack wird auf dem Task gespeichert, wo die Ausnahmen aufgetreten sind. Wenn ein Task mit nicht abgefangenen Ausnahmen fehlschlägt, kann current_exceptions(task) verwendet werden, um den Ausnahme-Stack für diesen Task zu inspizieren.

Comparison with backtrace

Ein Aufruf von backtrace gibt einen Vektor von Union{Ptr{Nothing}, Base.InterpreterIP} zurück, der dann in stacktrace zur Übersetzung übergeben werden kann:

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

Beachten Sie, dass der Vektor, der von backtrace zurückgegeben wurde, 18 Elemente hatte, während der Vektor, der von stacktrace zurückgegeben wurde, nur 6 hat. Dies liegt daran, dass 4d61726b646f776e2e436f64652822222c2022737461636b74726163652229_40726566 standardmäßig alle niedrigeren C-Funktionen vom Stack entfernt. Wenn Sie Stack-Frames von C-Aufrufen einbeziehen möchten, können Sie es so machen:

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

Individuelle Zeiger, die von backtrace zurückgegeben werden, können in StackTraces.StackFrame übersetzt werden, indem sie in StackTraces.lookup übergeben werden:

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!