Stack Traces

يوفر نموذج StackTraces تتبعات بسيطة للمكدس تكون قابلة للقراءة البشرية وسهلة الاستخدام برمجيًا.

Viewing a stack trace

الدالة الأساسية المستخدمة للحصول على تتبع المكدس هي 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 s. لتسهيل الاستخدام، يمكن استخدام الاسم المستعار 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. عند استدعاء 4d61726b646f776e2e436f64652822222c2022737461636b747261636528292229_40726566 من 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})() at event.jl:92

Extracting useful information

كل 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

هذا يجعل معلومات تتبع المكدس متاحة برمجيًا للتسجيل، ومعالجة الأخطاء، وأكثر من ذلك.

Error handling

بينما يمكن أن يكون الوصول السهل إلى المعلومات حول الحالة الحالية لمكدس الاستدعاءات مفيدًا في العديد من الأماكن، فإن التطبيق الأكثر وضوحًا هو في معالجة الأخطاء وتصحيح الأخطاء.

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 مفقود تمامًا. هذا مفهوم، نظرًا لأن 4d61726b646f776e2e436f64652822222c2022737461636b74726163652229_40726566 يتم استدعاؤه من سياق catch. بينما في هذا المثال، من السهل نسبيًا العثور على المصدر الفعلي للخطأ، في الحالات المعقدة يصبح تتبع مصدر الخطأ غير تافه.

يمكن معالجة ذلك عن طريق تمرير نتيجة catch_backtrace إلى stacktrace. بدلاً من إرجاع معلومات مكدس الاستدعاءات للسياق الحالي، فإن 4d61726b646f776e2e436f64652822222c202263617463685f6261636b74726163652229_40726566 يعيد معلومات المكدس لسياق الاستثناء الأكثر حداثة:

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

Exception stacks and current_exceptions

Julia 1.1

تتطلب مكدسات الاستثناءات على الأقل جوليا 1.1.

أثناء التعامل مع استثناء، قد يتم طرح استثناءات إضافية. قد يكون من المفيد فحص جميع هذه الاستثناءات لتحديد السبب الجذري للمشكلة. يدعم وقت تشغيل جوليا ذلك من خلال دفع كل استثناء إلى مكدس الاستثناءات الداخلي عند حدوثه. عندما يخرج الكود من 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) لفحص مكدس الاستثناءات لتلك المهمة.

Comparison with backtrace

A call to backtrace returns a vector of Union{Ptr{Nothing}, Base.InterpreterIP}, which may then be passed into stacktrace for translation:

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. وذلك لأن 4d61726b646f776e2e436f64652822222c2022737461636b74726163652229_40726566 يقوم افتراضيًا بإزالة أي دوال 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.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!