堆疊追蹤

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})() at event.jl:92

呼叫 stacktrace() 會傳回一個 StackTraces.StackFrame 的向量。為了方便使用,別名 StackTraces.StackTrace 可用來取代 Vector{StackFrame}。(帶有 [...] 的範例表示輸出可能會根據程式碼的執行方式而有所不同。)

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 的框架。當從 REPL 呼叫 stacktrace() 時,你還會在堆疊中看到一些來自 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})() at event.jl:92

擷取有用的資訊

每個 StackTraces.StackFrame 都包含函式名稱、檔案名稱、行號、lambda 資訊、一個標誌,表示框架是否已內嵌,一個標誌,表示它是否為 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})() 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

在此範例中,根本原因例外狀況 (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})() at 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})() 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

backtrace 傳回的個別指標可以傳遞到 StackTraces.lookup 中,轉換成 StackTraces.StackFrame s

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!