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")==0Calling 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 hereDebugging 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 حتى تتمكن من التقاط الخطأ.