gdb debugging tips
Displaying Julia variables
داخل gdb
، يمكن عرض أي كائن jl_value_t*
obj
باستخدام
(gdb) call jl_(obj)
سيتم عرض الكائن في جلسة julia
، وليس في جلسة gdb. هذه طريقة مفيدة لاكتشاف الأنواع والقيم للكائنات التي يتم التعامل معها بواسطة كود C الخاص بـ Julia.
بالمثل، إذا كنت تقوم بتصحيح بعض من تفاصيل جوليّا (مثل compiler.jl
)، يمكنك طباعة obj
باستخدام
ccall(:jl_, Cvoid, (Any,), obj)
هذه طريقة جيدة لتجاوز المشكلات التي تنشأ من ترتيب تهيئة تدفقات الإخراج في جوليا.
يستخدم مترجم فليسب لجوليا كائنات value_t
؛ يمكن عرض هذه الكائنات باستخدام call fl_print(fl_ctx, ios_stdout, obj)
.
Useful Julia variables for Inspecting
بينما يمكن أن تكون عناوين العديد من المتغيرات، مثل الساكنات، مفيدة للطباعة للعديد من الأخطاء، هناك عدد من المتغيرات الإضافية (انظر julia.h
للحصول على قائمة كاملة) التي تكون أكثر فائدة.
- (عند
jl_apply_generic
)mfunc
وjl_uncompress_ast(mfunc->def, mfunc->code)
:: لفهم القليل عن مكدس الاستدعاءات jl_lineno
وjl_filename
:: لتحديد السطر الذي يجب أن نبدأ منه تصحيح الأخطاء في اختبار ما (أو لمعرفة مدى عمق الملف الذي تم تحليله)$1
:: ليس في الحقيقة متغيرًا، ولكنه لا يزال اختصارًا مفيدًا للإشارة إلى نتيجة آخر أمر gdb (مثلprint
)jl_options
:: أحيانًا مفيد، لأنه يسرد جميع خيارات سطر الأوامر التي تم تحليلها بنجاحjl_uv_stderr
:: لأنه لا أحد يحب أن يكون غير قادر على التفاعل مع stdio
Useful Julia functions for Inspecting those variables
jl_print_task_backtraces(0)
:: مشابه لأمرthread apply all bt
في gdb أوthread backtrace all
في lldb. يقوم بتشغيل جميع الخيوط مع طباعة تتبعات العودة لجميع المهام الموجودة.jl_gdblookup($pc)
:: للبحث عن الدالة الحالية والسطر.jl_gdblookupinfo($pc)
:: للبحث عن كائن مثيل الطريقة الحالية.jl_gdbdumpcode(mi)
:: لتفريغ جميعcode_typed/code_llvm/code_asm
عندما لا يعمل REPL بشكل صحيح.jlbacktrace()
:: لطباعة مكدس تتبع الأخطاء الحالي في جوليا إلى stderr. يمكن استخدامه فقط بعد استدعاءrecord_backtrace()
.jl_dump_llvm_value(Value*)
:: لاستدعاءValue->dump()
في gdb، حيث لا يعمل بشكل أصلي. على سبيل المثال،f->linfo->functionObject
،f->linfo->specFunctionObject
، وto_function(f->linfo)
.jl_dump_llvm_module(Module*)
:: لاستدعاءModule->dump()
في gdb، حيث لا يعمل بشكل أصلي.Type->dump()
:: يعمل فقط في lldb. ملاحظة: أضف شيئًا مثل;1
لمنع lldb من طباعة موجهه فوق الإخراجjl_eval_string("expr")
:: لاستدعاء الآثار الجانبية لتعديل الحالة الحالية أو للبحث عن الرموزjl_typeof(jl_value_t*)
:: لاستخراج علامة نوع قيمة جوليا (في gdb، قم باستدعاءmacro define jl_typeof jl_typeof
أولاً، أو اختر شيئًا قصيرًا مثلty
كأول وسيط لتعريف اختصار)
Inserting breakpoints for inspection from gdb
في جلسة gdb
الخاصة بك، قم بتعيين نقطة توقف في jl_breakpoint
على النحو التالي:
(gdb) break jl_breakpoint
ثم داخل كود جوليا الخاص بك، أضف استدعاء إلى jl_breakpoint
عن طريق إضافة
ccall(:jl_breakpoint, Cvoid, (Any,), obj)
حيث يمكن أن يكون obj
أي متغير أو مجموعة تريد أن تكون متاحة في نقطة التوقف.
من المفيد بشكل خاص الرجوع إلى إطار jl_apply
، من حيث يمكنك عرض المعاملات لدالة باستخدام، على سبيل المثال،
(gdb) call jl_(args[0])
إطار آخر مفيد هو to_function(jl_method_instance_t *li, bool cstyle)
. حجة jl_method_instance_t*
هي بنية تحتوي على مرجع إلى شجرة التركيب النهائية المرسلة إلى المترجم. ومع ذلك، ستكون شجرة التركيب في هذه المرحلة عادةً مضغوطة؛ لمشاهدة شجرة التركيب، قم باستدعاء jl_uncompress_ast
ثم مرر النتيجة إلى jl_
:
#2 0x00007ffff7928bf7 in to_function (li=0x2812060, cstyle=false) at codegen.cpp:584
584 abort();
(gdb) p jl_(jl_uncompress_ast(li, li->ast))
Inserting breakpoints upon certain conditions
Loading a particular file
لنقل أن الملف هو sysimg.jl
:
(gdb) break jl_load if strcmp(fname, "sysimg.jl")==0
Calling a particular method
(gdb) break jl_apply_generic if strcmp((char*)(jl_symbol_name)(jl_gf_mtable(F)->name), "method_to_break")==0
نظرًا لأن هذه الدالة تُستخدم في كل استدعاء، ستجعل كل شيء أبطأ بمقدار 1000 مرة إذا قمت بذلك.
Dealing with signals
تتطلب جوليا بعض الإشارات لتعمل بشكل صحيح. يستخدم المحلل SIGUSR2
لأغراض أخذ العينات، ويستخدم جامع القمامة SIGSEGV
لمزامنة الخيوط. إذا كنت تقوم بتصحيح بعض الأكواد التي تستخدم المحلل أو عدة خيوط، قد ترغب في السماح للمصحح بتجاهل هذه الإشارات لأنها يمكن أن تُ triggered بشكل متكرر أثناء العمليات العادية. الأمر للقيام بذلك في GDB هو (استبدل SIGSEGV
بـ SIGUSR2
أو أي إشارات أخرى تريد تجاهلها):
(gdb) handle SIGSEGV noprint nostop pass
الأمر المقابل لـ LLDB هو (بعد بدء العملية):
(lldb) pro hand -p true -s false -n false SIGSEGV
إذا كنت تقوم بتصحيح خطأ segfault مع كود متعدد الخيوط، يمكنك تعيين نقطة توقف على jl_critical_error
(يجب أن تعمل sigdie_handler
أيضًا على Linux و BSD) من أجل التقاط خطأ segfault الفعلي فقط بدلاً من نقاط تزامن GC.
Debugging during Julia's build process (bootstrap)
الأخطاء التي تحدث أثناء make
تحتاج إلى معالجة خاصة. يتم بناء جوليا على مرحلتين، حيث يتم إنشاء sys0
و sys.ji
. لرؤية الأوامر التي تعمل في وقت الفشل، استخدم make VERBOSE=1
.
في وقت كتابة هذا، يمكنك تصحيح أخطاء البناء خلال مرحلة sys0
من الدليل base
باستخدام:
julia/base$ gdb --args ../usr/bin/julia-debug -C native --build ../usr/lib/julia/sys0 sysimg.jl
قد تحتاج إلى حذف جميع الملفات في usr/lib/julia/
لجعل هذا يعمل.
يمكنك تصحيح مرحلة sys.ji
باستخدام:
julia/base$ gdb --args ../usr/bin/julia-debug -C native --build ../usr/lib/julia/sys -J ../usr/lib/julia/sys0.ji sysimg.jl
بشكل افتراضي، ستؤدي أي أخطاء إلى خروج جوليا، حتى تحت gdb. لالتقاط خطأ "في الفعل"، قم بتعيين نقطة توقف في jl_error
(هناك عدة نقاط مفيدة أخرى، لأنواع محددة من الفشل، بما في ذلك: jl_too_few_args
، jl_too_many_args
، و jl_throw
).
بمجرد أن يتم التقاط خطأ، فإن تقنية مفيدة هي السير في المكدس وفحص الدالة من خلال فحص الاستدعاء المتعلق بـ jl_apply
. لأخذ مثال من العالم الحقيقي:
Breakpoint 1, jl_throw (e=0x7ffdf42de400) at task.c:802
802 {
(gdb) p jl_(e)
ErrorException("auto_unbox: unable to determine argument type")
$2 = void
(gdb) bt 10
#0 jl_throw (e=0x7ffdf42de400) at task.c:802
#1 0x00007ffff65412fe in jl_error (str=0x7ffde56be000 <_j_str267> "auto_unbox:
unable to determine argument type")
at builtins.c:39
#2 0x00007ffde56bd01a in julia_convert_16886 ()
#3 0x00007ffff6541154 in jl_apply (f=0x7ffdf367f630, args=0x7fffffffc2b0, nargs=2) at julia.h:1281
...
أحدث jl_apply
هو في الإطار #3، لذا يمكننا العودة إلى هناك والنظر في AST للدالة julia_convert_16886
. هذا هو الاسم الفريد لبعض طرق convert
. f
في هذا الإطار هو jl_function_t*
، لذا يمكننا النظر في توقيع النوع، إن وجد، من حقل specTypes
:
(gdb) f 3
#3 0x00007ffff6541154 in jl_apply (f=0x7ffdf367f630, args=0x7fffffffc2b0, nargs=2) at julia.h:1281
1281 return f->fptr((jl_value_t*)f, args, nargs);
(gdb) p f->linfo->specTypes
$4 = (jl_tupletype_t *) 0x7ffdf39b1030
(gdb) p jl_( f->linfo->specTypes )
Tuple{Type{Float32}, Float64} # <-- type signature for julia_convert_16886
ثم يمكننا النظر إلى شجرة التحليل (AST) لهذه الدالة:
(gdb) p jl_( jl_uncompress_ast(f->linfo, f->linfo->ast) )
Expr(:lambda, Array{Any, 1}[:#s29, :x], Array{Any, 1}[Array{Any, 1}[], Array{Any, 1}[Array{Any, 1}[:#s29, :Any, 0], Array{Any, 1}[:x, :Any, 0]], Array{Any, 1}[], 0], Expr(:body,
Expr(:line, 90, :float.jl)::Any,
Expr(:return, Expr(:call, :box, :Float32, Expr(:call, :fptrunc, :Float32, :x)::Any)::Any)::Any)::Any)::Any
أخيرًا، وربما بشكل أكثر فائدة، يمكننا إجبار الدالة على إعادة الترجمة من أجل المرور عبر عملية توليد الشيفرة. للقيام بذلك، قم بإزالة الكائن المخزن مؤقتًا functionObject
من jl_lamdbda_info_t*
:
(gdb) p f->linfo->functionObject
$8 = (void *) 0x1289d070
(gdb) set f->linfo->functionObject = NULL
ثم، قم بتعيين نقطة توقف في مكان مفيد (مثل emit_function
، emit_expr
، emit_call
، إلخ)، وشغل توليد الشيفرة:
(gdb) p jl_compile(f)
... # your breakpoint here
Debugging precompilation errors
تقوم عملية ما قبل التجميع بإنشاء عملية جوليا منفصلة لتجميع كل وحدة. يتطلب تعيين نقطة توقف أو التقاط الأخطاء في عامل ما قبل التجميع توصيل مصحح الأخطاء بالعامل. أسهل طريقة هي تعيين مصحح الأخطاء لمراقبة إطلاق العمليات الجديدة التي تتطابق مع اسم معين. على سبيل المثال:
(gdb) attach -w -n julia-debug
أو:
(lldb) process attach -w -n julia-debug
ثم قم بتشغيل نص/أمر لبدء ما قبل التجميع. كما هو موضح سابقًا، استخدم نقاط التوقف الشرطية في العملية الأصلية لالتقاط أحداث تحميل الملفات المحددة وتضييق نافذة التصحيح. (قد تتطلب بعض أنظمة التشغيل طرقًا بديلة، مثل متابعة كل fork
من العملية الأصلية)
Mozilla's Record and Replay Framework (rr)
تعمل جوليا الآن خارج الصندوق مع rr، وهو إطار عمل خفيف للتسجيل وتصحيح الأخطاء الحتمي من موزيلا. يتيح لك ذلك إعادة تشغيل تتبع تنفيذ بشكل حتمي. إن مساحات عنوان التنفيذ المعاد تشغيله، ومحتويات السجلات، وبيانات استدعاء النظام، إلخ، هي بالضبط نفسها في كل تشغيل.
يتطلب إصدار حديث من rr (3.1.0 أو أعلى).
Reproducing concurrency bugs with rr
rr يحاكي آلة ذات خيط واحد بشكل افتراضي. من أجل تصحيح الشيفرة المتزامنة، يمكنك استخدام rr record --chaos
الذي سيسبب لـ rr بمحاكاة ما بين نواة واحدة إلى ثماني نوى، يتم اختيارها عشوائيًا. لذلك قد ترغب في تعيين JULIA_NUM_THREADS=8
وإعادة تشغيل الشيفرة الخاصة بك تحت rr حتى تتمكن من التقاط الخطأ.